Sunday, November 19, 2017

MySQL Cluster 7.5 Kurulumu

Giriş


Veriler bir kurumun en önemli varlığıdır. Kurumsal uygulamaların hemen hemen hepsi kullanım senaryolarının bir adımında veri tabanına erişim ihtiyacı duyarlar. Bu nedenle kurumsal mimari şekillendirilirken, veri tabanlarının hem dar boğaz oluşturmayacak şekilde ölçeklenebilir olmasına hem de iş süreçlerini kesintiye uğratmayacak şekilde her zaman erişilebilir olmasına dikkat edilir. Genel olarak ölçeklenebilirlik ve her zaman erişilebilirlik için kümeleme çözümlerinden yararlanılır. Veri tabanlarının çoğunun birer kümeleme çözümü vardır. Ancak kümeleme yaklaşımları farklılık gösterebilir. Burada temelde iki farklı yaklaşım kullanılır: Kaynak Paylaşımsız (=Shared-Nothing) Yaklaşım ve Disk Paylaşımlı (=Shared Disk) Yaklaşım. Kaynak paylaşımsız yaklaşımda, kümeyi oluşturan düğümler, sıradan bilgisayarlar kullanılarak oluşturulur. Ortak paylaşılan bir disk alanına ya da özel bir donanıma ihtiyaç duyulmaz. Elbette düğümleri biri birine bağlayacak bir bilgisayar ağ yapısına ihtiyaç bulunur. Disk paylaşımlı yapıda ise SAN (=Storage Area Network) ya da NAS (Network-Attached Storage) gibi özel bir depolama çözümüne ihtiyaç duyulur. MySQL Cluster kaynak paylaşımsız yaklaşımı kullanırken, Oracle RAC ise disk paylaşımlı yaklaşımı kullanır. Yazmalarda kaynak paylaşımsız çözüm dağıtık kilit (=Distributed Locking) mekanizmasını kullandığı için potansiyel olarak daha ölçeklenebilir bir çözüm sunabilir. Dağıtık kilit mekanizmasında, genel olarak iki evreli kilit (=2PL, Two-Phase Lock) mekanizması kullanılır. Benzer şekilde hareket (=transaction) yönetiminde ise iki evreli onay (=2PC, Two Phase Commit) hareket algoritması kullanılır. 2PC algoritmasında, hareketi yönetecek bir koordinatöre (TC, Transaction Coordinator) ihtiyaç vardır. TC, harekette yer alan düğümlere bir ön onay isteği gönderir. Eğer tüm düğümler olumlu cevap dönerse, TC tüm düğümlere asıl onayı gönderir. Eğer düğümlerden en az biri olumsuz cevap gönderirse, bu sefer TC düğümlere geri al (=Rollback) komutu gönderir. Genellikle kümeyi oluşturan düğümler TC rolünü üstlenirler. 

Her dağıtık sistemin ilgilenmesi gereken 3 problem vardır:
  • Tutarlılık: Kümedeki her düğüm aynı veriye sahip olmalıdır
  • Erişilebilirlik: Her istek için işlem başarılı olsun ya da olmasın bir cevap dönülmelidir
  • Dayanıklılık: Kümedeki düğümlerde ya da iletişimde oluşan hatalara karşı sistemin çalışmaya devam edebilme yeteneği
Bu üç özelliği her durumda sağlamak mümkün değildir. MySQL her türlü hataya karşı dayanıklı değildir. Eğer bilgisayar ağında bazı düğümler biri birleri ile iletişim kurabilirken, bazı düğümler biri birlerini görmüyorsa MySQL Cluster, tutarlılığı, dayanıklılığa tercih eder.

MySQL Cluster Mimarisi


MySQL Cluster kayıtları bellekte saklayan dağıtık bir ilişkisel veri tabanıdır. Bellekte saklanan veriler indekslenmiş sütunlar ve bunların indeksleri bellekte saklanır. Bu nedenle indekslenmiş alan üzerinden veriye erişim çok hızlıdır. Veriler birden fazla düğümde yedeklenir. Böylelikle kümedeki bir düğümün bozulması veri kaybına neden olmaz.

MySQL Cluster içinde üç tür düğüm bulunmaktadır:
  • Veri düğümü (=Data Node)
Verileri ve indekslerini bellekte saklamaktan ve erişiminden sorumludur.Veri düğümlerinden NoOfReplicas değişkeni kadarı bir araya gelip bir Düğüm Grubu oluştururlar. Aynı Düğüm Grubunda yer alan veri düğümler, biri diğeri üzerindeki kayıtları yedekli olarak saklar. Böylelikle, gruptaki veri düğümlerinden biri servis dışı kalırsa, küme kesintisiz çalışmaya devam eder. Tipik olarak NoOfReplicas değişkeninin değeri 2 olarak seçilir.
  • Yönetim Düğümü (=Management Node)
Kümelemenin yapılandırılmasından, düğümlerin uzaktan yönetilmesinden ve izlenmesinden sorumludur. Veri düğümleri açılış sırasında yönetim düğümü ile konuşurlar. Yapılandırma değişikliklerinde yönetim düğümü önemli bir rol oynar. Bu nedenle, her zaman erişilebilirlik için yönetim düğümü de en az iki adet olarak artıklık yaratacak şekilde tasarlanmalıdır.
  • API/SQL Düğümü (=API/SQL Node)
Kümeden hizmet almak isteyen istemciler, doğrudan veri düğümlerine erişebilecekleri gibi, API/SQL düğümlerine bağlanıp SQL cümlelerini çalıştırabilirler. Bu düğümler üzerinde NDB saklama motoru bulunduran, küme ile ilgili herhangi bir sorumlulukları bulunmayan, normal birer MySQL sunucusudurlar.
Bu yazıda, aşağıdaki çizgede gösterildiği şekilde, iki yönetim düğümlü, dört veri düğümlü ve iki SQL düğümlü bir MySQL Cluster 7.5 kurulumu hem Windows hem de Centos/RHEL 7 işletim sistemi için anlatılacaktır.

Kurulumu yapılacak MySQL Cluster'ın Mimarisi

MySQL Cluster 7.5'in Windows Kurulumu


MySQL Cluster 7.5 sürümünü bu adresten Windows işletim sistemi (V971153-01.zip) için indirebilirsiniz. Windows kurulumu makinamda C:\demo dizinine yapacağım. V971153-01.zip dosyasından çıkan mysql-cluster-advanced-7.5.8-winx64.zip dosyasını c:\demo altına kopyalayınız ve ardından c:\demo\mysql-cluster-advanced-7.5.8 dizinine açınız:


Kurulum sonrasında c:\demo\mysql-cluster-advanced-7.5.8 dizininin içeriği

Kurulum adımlarını daha kolay gösterebilmek için tüm düğümleri aynı makina üzerinde gerçekleştireceğim.  Bu nedenle c:\demo\mysql-cluster-advanced-7.5.8 dizini altında yönetim düğümlerinin kullanımı için conf1 ve conf2 ile mgm1 ve mgm2 dizinlerini, veri düğümlerinin kullanımı için data1, data2, data3 ve data4 dizinlerini ve son olarak SQL düğümlerinin kullanımı için sql1 ve sql2 isimli dizinleri yaratalım:


Veri düğümleri, yönetim düğümleri ve SQL düğümleri için yaratılan düğümlerden sonra kurulum dizininin görünümü 

Artık iki yönetim düğümlü, dört veri düğümlü ve iki SQL düğümlü MySQL Cluster kurulumunun yapılandırma dosyasını (c:\demo\mysql-cluster-advanced-7.5.8\config.ini) yaratalım:

[ndbd default]
NoOfReplicas=2
DataMemory=1G
IndexMemory=256M

[ndbd]
nodeid=1
hostname=192.168.1.102
datadir=c:/demo/mysql-cluster-advanced-7.5.8/data1

[ndbd]
nodeid=2
hostname=192.168.1.102
datadir=c:/demo/mysql-cluster-advanced-7.5.8/data2

[ndbd]
nodeid=3
hostname=192.168.1.102
datadir=c:/demo/mysql-cluster-advanced-7.5.8/data3

[ndbd]
nodeid=4
hostname=192.168.1.102
datadir=c:/demo/mysql-cluster-advanced-7.5.8/data4

[ndb_mgmd]
nodeid=50
hostname=192.168.1.102
PortNumber=2100
datadir=c:/demo/mysql-cluster-advanced-7.5.8/mgm1
ArbitrationRank=1

[ndb_mgmd]
nodeid=51
hostname=192.168.1.102
PortNumber=2110
datadir=c:/demo/mysql-cluster-advanced-7.5.8/mgm2
ArbitrationRank=1

[mysqld]
nodeid=101

[mysqld]
nodeid=102   

config.ini dosyasını hem c:\demo\mysql-cluster-advanced-7.5.8\conf1 dizinine hem de c:\demo\mysql-cluster-advanced-7.5.8\conf2 dizinine kopyalıyoruz. Artık yönetim düğümlerini başlatabiliriz. Birinci yönetim düğümü için aşağıdaki komutu çalıştıralım:

set PATH=c:\demo\mysql-cluster-advanced-7.5.8\bin;%PATH%
ndb_mgmd --configdir=c:\demo\mysql-cluster-advanced-7.5.8\conf1 -f c:\demo\mysql-cluster-advanced-7.5.8\conf1\config.ini --ndb-nodeid=50 --initial

İkinci yönetim düğümü için benzer şekilde aşağıdaki komutu çalıştıralım:

set PATH=c:\demo\mysql-cluster-advanced-7.5.8\bin;%PATH%
ndb_mgmd --configdir=c:\demo\mysql-cluster-advanced-7.5.8\conf2 -f c:\demo\mysql-cluster-advanced-7.5.8\conf2\config.ini --ndb-nodeid=51 --initial

Yönetim düğümüne ndb_mgm istemcisi ile bağlanıp sunucuların durumunu izleyebiliriz:

c:\demo\mysql-cluster-advanced-7.5.8\bin>ndb_mgm  -c 192.168.1.102:2100
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: 192.168.1.102:2100
Cluster Configuration
---------------------
[ndbd(NDB)]     4 node(s)
id=1 (not connected, accepting connect from 192.168.1.102)
id=2 (not connected, accepting connect from 192.168.1.102)
id=3 (not connected, accepting connect from 192.168.1.102)
id=4 (not connected, accepting connect from 192.168.1.102)

[ndb_mgmd(MGM)] 2 node(s)
id=50   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)
id=51   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)

[mysqld(API)]   2 node(s)
id=101 (not connected, accepting connect from any host)
id=102 (not connected, accepting connect from any host)

ndb_mgm>

Şimdi veri düğümlerini başlatabiliriz:


Veri Düğümlerinin Başlatılması


Dört veri düğümünü de başlattıktan sonra yönetim konsolundan durumu izleyebiliriz:

ndb_mgm> show
Cluster Configuration
---------------------
[ndbd(NDB)]     4 node(s)
id=1    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0, *)
id=2    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0)
id=3    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)
id=4    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)

[ndb_mgmd(MGM)] 2 node(s)
id=50   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)
id=51   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)

[mysqld(API)]   2 node(s)
id=101  @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)
id=102  @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)

Son olarak SQL düğümlerini başlatmalıyız. Bu normal bir MySQL sunucusunu başlatmaktan çok farklı değil. Sadece yapılandırma dosyasında, kümeleme ile ilgili iki özel tanımlama yapacağız:
  • ndbcluster saklama motorunu kullanıma açacağız
  • Yönetim düğümüne bağlanabilmesi için bağlantı karakter katarını tanımlayacağız
Birinci SQL düğümünün yapılandırma dosyası

c:\demo\mysql-cluster-advanced-7.5.8\my1.cnf
[mysqld]
ndbcluster
ndb-connectstring=192.168.1.102:2100,192.168.1.102:2110
basedir = c:/demo/mysql-cluster-advanced-7.5.8 
datadir = c:/demo/mysql-cluster-advanced-7.5.8/sql1 
character-set-server = utf8 
collation-server = utf8_unicode_ci 
connect_timeout = 60000 
default-storage-engine = innodb 
innodb_buffer_pool_size = 2GB 
innodb_file_per_table = 1 
innodb_flush_log_at_trx_commit = 1 
innodb_lock_wait_timeout = 10 
innodb_log_buffer_size = 8388608 
innodb_log_file_size = 5242880 
innodb_max_dirty_pages_pct = 90 
innodb_thread_concurrency = 8 
max_connect_errors = 20 
max_connections = 1000 
query_cache_size = 0 
query_cache_type = OFF 
query_cache_limit = 0
thread_cache_size = 8 
transaction_isolation = READ-COMMITTED
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON
lower_case_table_names = 1
innodb_io_capacity = 750
innodb_io_capacity_max = 1000

İkinci SQL düğümünün yapılandırma dosyası

c:\demo\mysql-cluster-advanced-7.5.8\my2.cnf
[mysqld]
ndbcluster
ndb-connectstring=192.168.1.102:2100,192.168.1.102:2110
port=3316
basedir = c:/demo/mysql-cluster-advanced-7.5.8 
datadir = c:/demo/mysql-cluster-advanced-7.5.8/sql2 
character-set-server = utf8 
collation-server = utf8_unicode_ci 
connect_timeout = 60000 
default-storage-engine = innodb 
innodb_buffer_pool_size = 2GB 
innodb_file_per_table = 1 
innodb_flush_log_at_trx_commit = 1 
innodb_lock_wait_timeout = 10 
innodb_log_buffer_size = 8388608 
innodb_log_file_size = 5242880 
innodb_max_dirty_pages_pct = 90 
innodb_thread_concurrency = 8 
max_connect_errors = 20 
max_connections = 1000 
query_cache_size = 0 
query_cache_type = OFF 
query_cache_limit = 0
thread_cache_size = 8 
transaction_isolation = READ-COMMITTED
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON
lower_case_table_names = 1
innodb_io_capacity = 750
innodb_io_capacity_max = 1000

Tüm düğümleri aynı makinada çalıştırmamız nedeni ile ikinci SQL düğümünün yapılandırma dosyasında port numarasını 3316 olarak tanımladık. Bu yapılandırma dosyaları ile artık SQL düğümlerini başlatabiliriz:


SQL Düğümlerin başlatılması
İki SQL düğümünün başarılı bir şekilde kümeleme çözümüne dahil olduğunu yönetim konsolundan durumu izleyebiliriz:

ndb_mgm> show
Cluster Configuration
---------------------
[ndbd(NDB)]     4 node(s)
id=1    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0, *)
id=2    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0)
id=3    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)
id=4    @192.168.1.102  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)

[ndb_mgmd(MGM)] 2 node(s)
id=50   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)
id=51   @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)

[mysqld(API)]   2 node(s)
id=101  @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)
id=102  @192.168.1.102  (mysql-5.7.20 ndb-7.5.8)


MySQL Cluster 7.5'in Centos/RHEL 7 Kurulumu

MySQL Cluster 7.5 sürümünü bu bağlantıdan CentOS/OEL/RHEL 7 platformları için (V971140-01.zip) indirebilirsiniz. Öncelikle CentOS 7 sürümünde MySQL ile ilgili herhangi bir kurulum varsa onu kaldırıyoruz:

guru@localhost Desktop]$ sudo rpm -qa | grep -i maria
mariadb-libs-5.5.44-2.el7.centos.x86_64
[guru@localhost Desktop]$ sudo rpm -e mariadb-libs
error: Failed dependencies:
 libmysqlclient.so.18()(64bit) is needed by (installed) postfix-2:2.10.1-6.el7.x86_64
 libmysqlclient.so.18(libmysqlclient_18)(64bit) is needed by (installed) postfix-2:2.10.1-6.el7.x86_64
[guru@localhost Desktop]$ sudo rpm -e postfix-2.10.1-6.el7.x86_64
[guru@localhost Desktop]$ sudo rpm -e mariadb-libs

Daha sonra V971140-01.zip dosyasından aşağıdaki listedeki RPM dosyalarını kurulumu yapabilmek için bir dizine açıyoruz:

[guru@localhost Downloads]$ ls
mysql-cluster-commercial-common-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-data-node-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-libs-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-libs-compat-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-management-server-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-ndbclient-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-server-7.5.8-1.1.el7.x86_64.rpm
mysql-cluster-commercial-client-7.5.8-1.1.el7.x86_64.rpm

Kuruluma başlayabilmek için iki paket bağımlılığını çözmeliyiz:

[guru@localhost Downloads]$ sudo yum install perl
Loaded plugins: fastestmirror, langpacks
Resolving Dependencies
--> Running transaction check
---> Package perl.x86_64 4:5.16.3-286.el7 will be updated
---> Package perl.x86_64 4:5.16.3-292.el7 will be an update
--> Processing Dependency: perl-libs = 4:5.16.3-292.el7 for package: 4:perl-5.16.3-292.el7.x86_64
--> Running transaction check
---> Package perl-libs.x86_64 4:5.16.3-286.el7 will be updated
---> Package perl-libs.x86_64 4:5.16.3-292.el7 will be an update
--> Finished Dependency Resolution

Dependencies Resolved

==============================================================================================================
 Package                   Arch                   Version                          Repository            Size
==============================================================================================================
Updating:
 perl                      x86_64                 4:5.16.3-292.el7                 base                 8.0 M
Updating for dependencies:
 perl-libs                 x86_64                 4:5.16.3-292.el7                 base                 688 k

Transaction Summary
==============================================================================================================
Upgrade  1 Package (+1 Dependent package)

Total download size: 8.6 M
Is this ok [y/d/N]: y
Downloading packages:
No Presto metadata available for base
(1/2): perl-libs-5.16.3-292.el7.x86_64.rpm                                             | 688 kB  00:00:05     
(2/2): perl-5.16.3-292.el7.x86_64.rpm                                                  | 8.0 MB  00:00:24     
--------------------------------------------------------------------------------------------------------------
Total                                                                         360 kB/s | 8.6 MB  00:00:24     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Updating   : 4:perl-libs-5.16.3-292.el7.x86_64                                                          1/4 
  Updating   : 4:perl-5.16.3-292.el7.x86_64                                                               2/4 
  Cleanup    : 4:perl-libs-5.16.3-286.el7.x86_64                                                          3/4 
  Cleanup    : 4:perl-5.16.3-286.el7.x86_64                                                               4/4 
  Verifying  : 4:perl-5.16.3-292.el7.x86_64                                                               1/4 
  Verifying  : 4:perl-libs-5.16.3-292.el7.x86_64                                                          2/4 
  Verifying  : 4:perl-libs-5.16.3-286.el7.x86_64                                                          3/4 
  Verifying  : 4:perl-5.16.3-286.el7.x86_64                                                               4/4 

Updated:
  perl.x86_64 4:5.16.3-292.el7                                                                                

Dependency Updated:
  perl-libs.x86_64 4:5.16.3-292.el7                                                                           

Complete!

[guru@localhost Downloads]$ sudo rpm -ivh ~/Downloads/perl-Class-MethodMaker-2.20-1.el7.x86_64.rpm 
Preparing...                          ################################# [100%]
Updating / installing...
   1:perl-Class-MethodMaker-2.20-1.el7################################# [100%]

[guru@localhost Downloads]$ sudo yum install perl-DBI
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
Resolving Dependencies
--> Running transaction check
---> Package perl-DBI.x86_64 0:1.627-4.el7 will be installed
--> Processing Dependency: perl(RPC::PlServer) >= 0.2001 for package: perl-DBI-1.627-4.el7.x86_64
--> Processing Dependency: perl(RPC::PlClient) >= 0.2000 for package: perl-DBI-1.627-4.el7.x86_64
--> Running transaction check
---> Package perl-PlRPC.noarch 0:0.2020-14.el7 will be installed
--> Processing Dependency: perl(Net::Daemon) >= 0.13 for package: perl-PlRPC-0.2020-14.el7.noarch
--> Processing Dependency: perl(Net::Daemon::Test) for package: perl-PlRPC-0.2020-14.el7.noarch
--> Processing Dependency: perl(Net::Daemon::Log) for package: perl-PlRPC-0.2020-14.el7.noarch
--> Processing Dependency: perl(Compress::Zlib) for package: perl-PlRPC-0.2020-14.el7.noarch
--> Running transaction check
---> Package perl-IO-Compress.noarch 0:2.061-2.el7 will be installed
--> Processing Dependency: perl(Compress::Raw::Zlib) >= 2.061 for package: perl-IO-Compress-2.061-2.el7.noarch
--> Processing Dependency: perl(Compress::Raw::Bzip2) >= 2.061 for package: perl-IO-Compress-2.061-2.el7.noarch
---> Package perl-Net-Daemon.noarch 0:0.48-5.el7 will be installed
--> Running transaction check
---> Package perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.el7 will be installed
---> Package perl-Compress-Raw-Zlib.x86_64 1:2.061-4.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

==============================================================================================================
 Package                              Arch                Version                     Repository         Size
==============================================================================================================
Installing:
 perl-DBI                             x86_64              1.627-4.el7                 base              802 k
Installing for dependencies:
 perl-Compress-Raw-Bzip2              x86_64              2.061-3.el7                 base               32 k
 perl-Compress-Raw-Zlib               x86_64              1:2.061-4.el7               base               57 k
 perl-IO-Compress                     noarch              2.061-2.el7                 base              260 k
 perl-Net-Daemon                      noarch              0.48-5.el7                  base               51 k
 perl-PlRPC                           noarch              0.2020-14.el7               base               36 k

Transaction Summary
==============================================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 1.2 M
Installed size: 3.1 M
Is this ok [y/d/N]: y
Downloading packages:
(1/6): perl-Compress-Raw-Zlib-2.061-4.el7.x86_64.rpm                                   |  57 kB  00:00:00     
(2/6): perl-Compress-Raw-Bzip2-2.061-3.el7.x86_64.rpm                                  |  32 kB  00:00:01     
(3/6): perl-PlRPC-0.2020-14.el7.noarch.rpm                                             |  36 kB  00:00:01     
(4/6): perl-Net-Daemon-0.48-5.el7.noarch.rpm                                           |  51 kB  00:00:03     
(5/6): perl-IO-Compress-2.061-2.el7.noarch.rpm                                         | 260 kB  00:00:03     
(6/6): perl-DBI-1.627-4.el7.x86_64.rpm                                                 | 802 kB  00:00:04     
--------------------------------------------------------------------------------------------------------------
Total                                                                         277 kB/s | 1.2 MB  00:00:04     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Warning: RPMDB altered outside of yum.
  Installing : perl-Compress-Raw-Bzip2-2.061-3.el7.x86_64                                                 1/6 
  Installing : 1:perl-Compress-Raw-Zlib-2.061-4.el7.x86_64                                                2/6 
  Installing : perl-IO-Compress-2.061-2.el7.noarch                                                        3/6 
  Installing : perl-Net-Daemon-0.48-5.el7.noarch                                                          4/6 
  Installing : perl-PlRPC-0.2020-14.el7.noarch                                                            5/6 
  Installing : perl-DBI-1.627-4.el7.x86_64                                                                6/6 
  Verifying  : perl-Net-Daemon-0.48-5.el7.noarch                                                          1/6 
  Verifying  : perl-IO-Compress-2.061-2.el7.noarch                                                        2/6 
  Verifying  : 1:perl-Compress-Raw-Zlib-2.061-4.el7.x86_64                                                3/6 
  Verifying  : perl-DBI-1.627-4.el7.x86_64                                                                4/6 
  Verifying  : perl-Compress-Raw-Bzip2-2.061-3.el7.x86_64                                                 5/6 
  Verifying  : perl-PlRPC-0.2020-14.el7.noarch                                                            6/6 

Installed:
  perl-DBI.x86_64 0:1.627-4.el7                                                                               

Dependency Installed:
  perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.el7           perl-Compress-Raw-Zlib.x86_64 1:2.061-4.el7          
  perl-IO-Compress.noarch 0:2.061-2.el7                  perl-Net-Daemon.noarch 0:0.48-5.el7                  
  perl-PlRPC.noarch 0:0.2020-14.el7                     

Complete!

Bu hazırlık kurulumlarından sonra MySQL Cluster 7.5'in kurulumunu yapabiliriz:

[guru@localhost Downloads]$ sudo rpm -ivh mysql-cluster-commercial-*
Preparing...                          ################################# [100%]
Updating / installing...
   1:mysql-cluster-commercial-common-7################################# [ 13%]
   2:mysql-cluster-commercial-libs-7.5################################# [ 25%]
   3:mysql-cluster-commercial-client-7################################# [ 38%]
   4:mysql-cluster-commercial-server-7################################# [ 50%]
   5:mysql-cluster-commercial-libs-com################################# [ 63%]
   6:mysql-cluster-commercial-ndbclien################################# [ 75%]
   7:mysql-cluster-commercial-manageme################################# [ 88%]
   8:mysql-cluster-commercial-data-nod################################# [100%]

Windows işletim sistemindeki kurulumda olduğu gibi CentOS 7 işletim sistemine kurulumda da tüm düğümler aynı makina üzerinde çalışacaklar. Bunun için ihtiyaç duyulan dizileri yaratıyoruz:

[guru@localhost ~]$ mkdir demo
[guru@localhost ~]$ cd demo/
[guru@localhost demo]$ mkdir config1
[guru@localhost demo]$ mkdir config2
[guru@localhost demo]$ vi config1/config.ini
[guru@localhost demo]$ mkdir data{1,2,3,4}
[guru@localhost demo]$ mkdir mgm{1,2}
[guru@localhost demo]$ mkdir api{1,2}

MySQL Cluster yapılandırma dosyasını /home/guru/demo/config.ini dosyasında oluşturalım:

[ndbd default]
NoOfReplicas=2
DataMemory=512M
IndexMemory=64M

[ndbd]
nodeid=1
hostname=node1
datadir=/home/guru/demo/data1

[ndbd]
nodeid=2
hostname=node2
datadir=/home/guru/demo/data2

[ndbd]
nodeid=3
hostname=node3
datadir=/home/guru/demo/data3

[ndbd]
nodeid=4
hostname=node4
datadir=/home/guru/demo/data4

[ndb_mgmd]
nodeid=50
hostname=mgm1
PortNumber=2100
datadir=/home/guru/demo/mgm1
ArbitrationRank=1

[ndb_mgmd]
nodeid=51
hostname=mgm2
PortNumber=2110

Yapılandırma dosyasında gözüken node1, node2, node3, node4, mgm1, mgm2, api1 ve api2 makinalarını /etc/hosts dosyasında statik olarak tanımlayacağız:

[guru@localhost demo]$ sudo vi /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.239.129 node1 node2 node3 node4 mgm1 mgm2 api1 api2

Önce yönetim düğümlerini daha sonra da veri düğümlerini sırayla çalıştıralım:

[guru@localhost demo]$ ndb_mgmd --configdir=/home/guru/demo/config1 -f /home/guru/demo/config1/config.ini --ndb-nodeid=50 --initial
MySQL Cluster Management Server mysql-5.7.20 ndb-7.5.8
[guru@localhost demo]$ ndb_mgmd --configdir=/home/guru/demo/config2 -f /home/guru/demo/config2/config.ini --ndb-nodeid=51 --initial
MySQL Cluster Management Server mysql-5.7.20 ndb-7.5.8

[guru@localhost demo]$ ndbd -c mgm1:2100,mgm2:2110
2017-11-19 15:11:40 [ndbd] INFO     -- Angel connected to 'mgm1:2100'
2017-11-19 15:11:40 [ndbd] INFO     -- Angel allocated nodeid: 1
[guru@localhost demo]$ ndbd -c mgm1:2100,mgm2:2110
2017-11-19 15:11:44 [ndbd] INFO     -- Angel connected to 'mgm1:2100'
2017-11-19 15:11:44 [ndbd] INFO     -- Angel allocated nodeid: 2
[guru@localhost demo]$ ndbd -c mgm1:2100,mgm2:2110
2017-11-19 15:11:46 [ndbd] INFO     -- Angel connected to 'mgm1:2100'
2017-11-19 15:11:47 [ndbd] INFO     -- Angel allocated nodeid: 3
[guru@localhost demo]$ ndbd -c mgm1:2100,mgm2:2110
2017-11-19 15:11:50 [ndbd] INFO     -- Angel connected to 'mgm1:2100'
2017-11-19 15:11:50 [ndbd] INFO     -- Angel allocated nodeid: 4

Düğümlerin durumunu yönetim düğüm istemcisinden izleyebiliriz:

[guru@localhost demo]$ ndb_mgm -c mgm1:2100
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: mgm1:2100
Cluster Configuration
---------------------
[ndbd(NDB)] 4 node(s)
id=1 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0, *)
id=2 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0)
id=3 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)
id=4 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)

[ndb_mgmd(MGM)] 2 node(s)
id=50 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)
id=51 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)

[mysqld(API)] 2 node(s)
id=101 (not connected, accepting connect from any host)
id=102 (not connected, accepting connect from any host)

ndb_mgm> 

API düğümleri için ise önce /var/lib/mysql dizini altında api1 ve api2 adında mysql:mysql sahipliğinde iki dizin yaratalım ve daha sonra içini MySQL'in dosyaları ile dolduralım:

[guru@localhost demo]sudo mkdir /var/lib/mysql/api{1,2}
[guru@localhost demo]$ sudo mysqld --datadir /var/lib/mysql/api1 --initialize
[guru@localhost demo]$ sudo mysqld --datadir /var/lib/mysql/api2 --initialize
[guru@localhost demo]$ sudo chown -R mysql:mysql /home/guru/demo/api1
[guru@localhost demo]$ sudo chown -R mysql:mysql /home/guru/demo/api2

API MySQL sunucularına root parolasını bir kereliğine başlangıçta verebilmek için /var/lib/mysql/init-password isimli bir dosya oluşturalım:

[root@localhost mysql]# cat /var/lib/mysql/init-password 
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Secret_123';

Sunucuları otomatik başlatabilmek için systemd servisi olarak tanıtmamız uygun olur:

root@localhost mysql]# cat /usr/lib/systemd/system/api1.service 
[Unit]
Description=api1 service
After=syslog.target
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/mysqld --defaults-file=/var/lib/mysql/my1.cnf --init-file=/var/lib/mysql/init-password --log-error=/var/lib/mysql/api1/error.log
TimeoutSec=300
PrivateTmp=true
User=mysql
Group=mysql
WorkingDirectory=/usr

[Install]
WantedBy=multi-user.target

Servis olarak ekleyebilmek için ise komut satırından aşağıdaki komutları sırayla çalıştırmanız gerekir:

[guru@localhost demo]$ sudo systemctl enable api1
Created symlink from /etc/systemd/system/multi-user.target.wants/api1.service to /usr/lib/systemd/system/api1.service.
[guru@localhost demo]$ sudo systemctl start api1
[root@localhost mysql]# systemctl status  api1
● api1.service - api1 service
   Loaded: loaded (/usr/lib/systemd/system/api1.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2017-11-19 16:41:58 EET; 8s ago
 Main PID: 21314 (mysqld)
   CGroup: /system.slice/api1.service
           └─21314 /usr/sbin/mysqld --defaults-file=/var/lib/mysql/my1.cnf --init-file=/var/lib/mysql/init-password --log-error=/var/lib/mysql/api1/error.log

İkinci API düğümünün yapılandırma dosyasında, düğümler aynı makinada olduğu için birinci API düğümünün yapılandırma dosyasına göre üç farklılık bulunuyor:
  • Port numarası
  • Soket dosyası
  • Veri dizini

[mysqld]
ndbcluster
ndb-connectstring=mgm1:2100,mgm2:2110
port=3316
socket= /var/lib/mysql/mysql.api2.sock
datadir = /var/lib/mysql/api2
character-set-server = utf8
collation-server = utf8_unicode_ci
connect_timeout = 60000
default-storage-engine = innodb
innodb_buffer_pool_size = 2GB
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 10
innodb_log_buffer_size = 8388608
innodb_log_file_size = 5242880
innodb_max_dirty_pages_pct = 90
innodb_thread_concurrency = 8
max_connect_errors = 20
max_connections = 1000
query_cache_size = 0
query_cache_type = OFF
query_cache_limit = 0
thread_cache_size = 8
transaction_isolation = READ-COMMITTED
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON
lower_case_table_names = 1
innodb_io_capacity = 750
innodb_io_capacity_max = 1000

İkinci SQL düğümünü de çalıştıralım:

[root@localhost mysql]# systemctl enable  api2
Created symlink from /etc/systemd/system/multi-user.target.wants/api2.service to /usr/lib/systemd/system/api2.service.
[root@localhost mysql]# sudo systemctl start api2
[root@localhost mysql]# systemctl status  api2
● api2.service - api1 service
   Loaded: loaded (/usr/lib/systemd/system/api2.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2017-11-19 20:55:29 EET; 31s ago
 Main PID: 29133 (mysqld)
   CGroup: /system.slice/api2.service
           └─29133 /usr/sbin/mysqld --defaults-file=/var/lib/mysql/my2.cnf --init-file=/var/lib/mysql/init-password --log-error=/var/lib/mysql/api2/error.log

Tüm MySQL Cluster düğümlerinin sorunsuz çalıştığını yönetim düğümüne konsoldan bağlanarak izleyebiliriz:

[root@localhost mysql]# ndb_mgm -c mgm1:2100
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: mgm1:2100
Cluster Configuration
---------------------
[ndbd(NDB)] 4 node(s)
id=1 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0, *)
id=2 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 0)
id=3 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)
id=4 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8, Nodegroup: 1)

[ndb_mgmd(MGM)] 2 node(s)
id=50 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)
id=51 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)

[mysqld(API)] 2 node(s)
id=101 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)
id=102 @192.168.239.129  (mysql-5.7.20 ndb-7.5.8)

World Veritabanının NDB Saklama Motoruna Yüklenmesi


Bu bölümde InnoDB saklama motorundaki world veritabanını NDB saklama motoruna nasıl taşınabileceğini inceleyeceğiz:
  1. world veritabanını yaratıyoruz.
  2. world_innodb.sql dosyasındaki yedeği world veritabanına geri açıyoruz. 
  3. city ve countrylanguage tablolarındaki ikincil anahtarları kaldırıyoruz. 
  4. Tabloların saklama motorunu alter table cümlesi ile değiştiriyoruz.
  5. city ve countrylanguage tablolarındaki ikincil anahtarları tekrar ekliyoruz.
Yukarıda sıralanan adımları aşağıdaki konsol çıktısından takip edebilirsiniz:

[root@localhost mysql]# mysql -uroot -pSecret_123
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 6
Server version: 5.7.20-ndb-7.5.8-cluster-commercial-advanced MySQL Cluster Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database world;
Query OK, 1 row affected (0.10 sec)

mysql> use world;
Database changed
mysql> source /home/guru/world_innodb.sql;

.
.
.

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql> show create table city;
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                                                                                                                   |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| city  | CREATE TABLE `city` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Name` char(35) NOT NULL DEFAULT '',
  `CountryCode` char(3) NOT NULL DEFAULT '',
  `District` char(20) NOT NULL DEFAULT '',
  `Population` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`ID`),
  KEY `CountryCode` (`CountryCode`)
) ENGINE=InnoDB AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> alter table city drop foreign key `city_ibfk_1`;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table countrylanguage;
+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table           | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                        |
+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| countrylanguage | CREATE TABLE `countrylanguage` (
  `CountryCode` char(3) NOT NULL DEFAULT '',
  `Language` char(30) NOT NULL DEFAULT '',
  `IsOfficial` enum('T','F') NOT NULL DEFAULT 'F',
  `Percentage` float(4,1) NOT NULL DEFAULT '0.0',
  PRIMARY KEY (`CountryCode`,`Language`),
  KEY `CountryCode` (`CountryCode`),
  CONSTRAINT `countryLanguage_ibfk_1` FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> alter table countrylanguage drop foreign key `countryLanguage_ibfk_1`;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table country engine=ndbcluster;
Query OK, 239 rows affected (0.44 sec)
Records: 239  Duplicates: 0  Warnings: 0

mysql> alter table city engine=ndbcluster;
Query OK, 4079 rows affected (0.47 sec)
Records: 4079  Duplicates: 0  Warnings: 0

mysql> alter table countrylanguage engine=ndbcluster;
Query OK, 984 rows affected (0.41 sec)
Records: 984  Duplicates: 0  Warnings: 0

mysql> alter table city add FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`);
Query OK, 0 rows affected (0.27 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table countrylanguage add FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`);
Query OK, 0 rows affected (0.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

Düğümlerin systemd Servisi Olarak Tanıtılması


MySQL Cluster'ı oluşturan düğümleri CentOS 7'de systemd servisi olarak tanımlamak için /usr/lib/systemd/system dizininde, aşağıda içeriği verilen servis tanımlarının oluşturulması gerekir:

mgm1.service:

[Unit]
Description=mgm1 service
After=syslog.target
After=network.target

[Service]
Type=forking
ExecStart=/sbin/ndb_mgmd --configdir=/var/lib/mysql/config1 -f /var/lib/mysql/config1/config.ini --ndb-nodeid=50
ExecReload=/sbin/ndb_mgmd --configdir=/var/lib/mysql/config1 -f /var/lib/mysql/config1/config.ini --ndb-nodeid=50 --reload
ExecStop=/sbin/ndb_mgm -e "50 stop"
Restart=always

[Install]
WantedBy=multi-user.target

mgm2.service:

[Unit]
Description=mgm1 service
After=syslog.target
After=network.target

[Service]
Type=forking
ExecStart=/sbin/ndb_mgmd --configdir=/var/lib/mysql/config2 -f /var/lib/mysql/config2/config.ini --ndb-nodeid=51
ExecReload=/sbin/ndb_mgmd --configdir=/var/lib/mysql/config2 -f /var/lib/mysql/config2/config.ini --ndb-nodeid=51 --reload
ExecStop=/sbin/ndb_mgm -e "51 stop"
Restart=always

[Install]
WantedBy=multi-user.target

node1.service:

[Unit]
Description=node 1 service
After=mgm1.service
After=mgm2.service

[Service]
Type=forking
ExecStart=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=1
ExecReload=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=1 --initial
ExecStop=/bin/ndb_mgm -c mgm1:2100,mgm2:2200 -e '1 stop'
Restart=always
User=mysql

[Install]
WantedBy=multi-user.target

node2.service:

[Unit]
Description=node 2 service
After=mgm1.service
After=mgm2.service

[Service]
Type=forking
ExecStart=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=2
ExecReload=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=2 --initial
ExecStop=/bin/ndb_mgm -c mgm1:2100,mgm2:2200 -e '2 stop'
Restart=always
User=mysql

[Install]
WantedBy=multi-user.target

node3.service:

[Unit]
Description=node 3 service
After=mgm1.service
After=mgm2.service

[Service]
Type=forking
ExecStart=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=3
ExecReload=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=3 --initial
ExecStop=/bin/ndb_mgm -c mgm1:2100,mgm2:2200 -e '3 stop'
Restart=always
User=mysql

[Install]
WantedBy=multi-user.target

node4.service:

[Unit]
Description=node 4 service
After=mgm1.service
After=mgm2.service

[Service]
Type=forking
ExecStart=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=4
ExecReload=/sbin/ndbd -c mgm1:2100,mgm2:2200 --ndb_nodeid=4 --initial
ExecStop=/bin/ndb_mgm -c mgm1:2100,mgm2:2200 -e '4 stop'
Restart=always
User=mysql

[Install]
WantedBy=multi-user.target

api1.service:

systemd servisleri arasında bağımlılıkları After ile tanımlayabilirsiniz. Buna göre api1 servisi başlatılmadan önce, eğer kapalı ise önce mgm1, mgm2, node1, node2, node3 ve node4 servisleri çalıştırılacaktır.

[Unit]
Description=api1 service
After=syslog.target
After=network.target
After=mgm1.target
After=mgm2.target
After=node1.target
After=node2.target
After=node3.target
After=node4.target

[Service]
Type=simple
ExecStart=/usr/sbin/mysqld --defaults-file=/var/lib/mysql/my1.cnf --init-file=/var/lib/mysql/init-password --log-error=/var/lib/mysql/api1/error.log
TimeoutSec=300
PrivateTmp=true
User=mysql
Group=mysql
WorkingDirectory=/usr

[Install]
WantedBy=multi-user.target

api2.service:

[Unit]
Description=api2 service
After=syslog.target
After=network.target
After=mgm1.target
After=mgm2.target
After=node1.target
After=node2.target
After=node3.target
After=node4.target

[Service]
Type=simple
ExecStart=/usr/sbin/mysqld --defaults-file=/var/lib/mysql/my2.cnf --init-file=/var/lib/mysql/init-password --log-error=/var/lib/mysql/api2/error.log
TimeoutSec=300
PrivateTmp=true
User=mysql
Group=mysql
WorkingDirectory=/usr

[Install]
WantedBy=multi-user.target

Bu servis tanımlarını etkinleştirmek üzere systemctl enable komutunu çalıştırmalısınız:

[root@node1 system]# systemctl enable node{1,2,3,4}.service
Created symlink from /etc/systemd/system/multi-user.target.wants/node1.service to /usr/lib/systemd/system/node1.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/node2.service to /usr/lib/systemd/system/node2.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/node3.service to /usr/lib/systemd/system/node3.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/node4.service to /usr/lib/systemd/system/node4.service.
[root@node1 system]# systemctl enable api{1,2}.service
Created symlink from /etc/systemd/system/multi-user.target.wants/api1.service to /usr/lib/systemd/system/api1.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/api2.service to /usr/lib/systemd/system/api2.service.
[root@node1 system]# systemctl enable mgm{1,2}.service
Created symlink from /etc/systemd/system/multi-user.target.wants/mgm1.service to /usr/lib/systemd/system/mgm1.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/mgm2.service to /usr/lib/systemd/system/mgm2.service.

Herhangi bir servisi kapatmak için systemctl stop komutunu kullanabilirsiniz:

[root@node1 system]# systemctl status mgm1.service
 mgm1.service - mgm1 service
   Loaded: loaded (/usr/lib/systemd/system/mgm1.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2017-11-21 22:15:48 EET; 22h ago
 Main PID: 1738 (ndb_mgmd)
   CGroup: /system.slice/mgm1.service
           └─1738 /sbin/ndb_mgmd --configdir=/var/lib/mysql/config1 -f /var/lib/mysql/config1/config.ini --ndb-nodeid=50

Nov 21 22:15:48 node1 systemd[1]: Starting mgm1 service...
Nov 21 22:15:48 node1 ndb_mgmd[1672]: MySQL Cluster Management Server mysql-5.7.20 ndb-7.5.8
Nov 21 22:15:48 node1 systemd[1]: Started mgm1 service.
[root@node1 system]# systemctl stop mgm1.service

Benzer şekilde servisi tekrar başlatmak için systemctl start komutunu kullanabilirsiniz:

[root@node1 system]# systemctl start mgm1.service
[root@node1 system]# systemctl status mgm1.service
 mgm1.service - mgm1 service
   Loaded: loaded (/usr/lib/systemd/system/mgm1.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2017-11-22 20:56:45 EET; 1s ago
  Process: 96590 ExecStop=/sbin/ndb_mgm -e 50 stop (code=exited, status=203/EXEC)
  Process: 98015 ExecStart=/sbin/ndb_mgmd --configdir=/var/lib/mysql/config1 -f /var/lib/mysql/config1/config.ini --ndb-nodeid=50 (code=exited, status=0/SUCCESS)
 Main PID: 98033 (ndb_mgmd)
   CGroup: /system.slice/mgm1.service
           └─98033 /sbin/ndb_mgmd --configdir=/var/lib/mysql/config1 -f /var/lib/mysql/config1/config.ini --ndb-nodeid=50

Nov 22 20:56:45 node1 systemd[1]: Starting mgm1 service...
Nov 22 20:56:45 node1 ndb_mgmd[98015]: MySQL Cluster Management Server mysql-5.7.20 ndb-7.5.8
Nov 22 20:56:45 node1 systemd[1]: Started mgm1 service.

Sunday, November 5, 2017

React, Angular 4, Vue Implementation of Mastermind Game

Mastermind is a simple number guessing game. Computer picks a 3-digit random number where all digits are distinct. This number is a secret and a player tries to find the secret by guessing. Computer guides the player with a hint message summarizing how much the guess is close the secret. Assume that the secret number is 549 and player's first move is 123. Computer evaluates the input 123 and produces "No Match!" message, hence there is no digit matched! Player's next move is 456Computer again evaluates the input 456 and produces the message "-2": The digits 4 and 5 are all matched but at the very wrong places! Player's next move is 567Computer again evaluates the input 567 and produces the message "+1": Only one digit is matched at the correct place! Player's next move is 584Computer again evaluates the input 584 and produces the message "+1-1"The digit 5 is matched at the correct place and the digit 4 is matched at the wrong place.  Player's next move is 540Computer again evaluates the input 540 and produces the message "+2": The digits 5 and 4 are all matched at the correct places! Finally the player inputs 549 and wins the game!

React JS Implementation

Game.js:
import React, {Component} from 'react';
import Table from './Table';
import Badge from './Badge';
import Alert from './Alert';
import Button from './Button';
import InputText from './InputText';
import Move from './Move';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

class Game extends Component {
    constructor(props) {
        super(props);
        this.state = {
            secret: 123, guess: 0, tries: 0, moves: [], wins: 0, loses: 0, total: 0, counter: 60, totalWinTime: 0,
            avgWinTime: 0, validationMessage: ""        };
        this.handleChange = this.handleChange.bind(this);
        this.play = this.play.bind(this);
    }

    tick() {
        this.setState({counter: this.state.counter - 1});
        if (this.state.counter <= 0) {
            this.setState({loses: this.state.loses + 1, total: this.state.total + 1});
            this.initGame("Time is out!");
        }
    }

    componentDidMount() {
        this.initGame();
        this.timerID = setInterval(() => this.tick(), 1000);
    }

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    handleChange(event) {
        this.setState({
            guess: event.target.value        })
    }

    initGame(message) {
        this.state.moves.splice(0);
        if (message !== undefined)
            this.state.moves.push(new Move(this.state.secret, message));
        this.setState({
            moves: this.state.moves,
            secret: this.createSecret(),
            counter: 60,
            tries: 0        });
    }

    play() {
        if (!Number.isInteger(Number(this.state.guess))) {
            this.setState({validationMessage: this.state.guess + " is not an integer!"});
            return;
        }
        if (Number(this.state.guess) < 0) {
            this.setState({validationMessage: this.state.guess + " is a negative integer!"});
            return;
        }
        if (this.state.guess.toString().length !== 3) {
            this.setState({validationMessage: this.state.guess + " is not a 3-digit integer!"});
            return;
        }
        for (let i in this.state.moves) {
            let move = this.state.moves[i];
            if (move.guess === this.state.guess) {
                this.setState({validationMessage: "Already played with " + this.state.guess + "!"});
                return;
            }
        }

        this.setState({tries: this.state.tries + 1});

        if (this.state.guess.localeCompare(this.state.secret) === 0) {
            let totalWinTime = this.state.totalWinTime + 60 - this.state.counter;
            let wins = this.state.wins + 1;
            this.setState({
                wins: wins,
                total: this.state.total + 1,
                totalWinTime: totalWinTime,
                avgWinTime: totalWinTime / wins,
                validationMessage: ""            });
            this.initGame("You win!");
        } else {
            let message = this.createMessage(this.state.guess, this.state.secret);
            this.state.moves.push(new Move(this.state.guess, message));
            this.setState({
                validationMessage: "",
                moves: this.state.moves            });
        }
    }

    createRandomDigit(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    createSecret() {
        let numbers = [this.createRandomDigit(1, 9)];
        while (numbers.length < 3) {
            let candidate = this.createRandomDigit(0, 9);
            if (numbers.indexOf(candidate) === -1) numbers.push(candidate);
        }
        return numbers.join('')
    }

    createMessage(guess, secret) {
        guess = Array.from(guess.toString());
        secret = Array.from(secret.toString());
        let perfectMatch = 0, partialMatch = 0;
        for (let i in guess) {
            let index = secret.indexOf(guess[i]);
            if (index === -1) continue;
            if (index === Number(i)) {
                perfectMatch++;
            } else {
                partialMatch++;
            }
        }
        if (!(perfectMatch || partialMatch)) return "No match.";
        let message = "";
        if (perfectMatch > 0) message = "+" + perfectMatch;
        if (partialMatch > 0) message += "-" + partialMatch;
        return message;
    }

    render() {
        return (
            <div className="container" role="main">
                <div className="panel panel-primary">
                    <div className="panel-heading">
                        <h3 className="panel-title">Mastermind Game</h3>
                    </div>
                    <div className="panel-body">
                        <InputText htmlFor="guess" label="Guess" value={this.state.guess} onChange={this.handleChange}/>
                        <Button label="Play" doClick={this.play}/>
                        <Alert valid={this.state.validationMessage.length > 0} message={this.state.validationMessage}/>
                        <Badge label="Tries" value={this.state.tries}/>
                        <Badge label="Counter" value={this.state.counter}/>
                        <Badge label="Wins" value={this.state.wins}/>
                        <Badge label="Loses" value={this.state.loses}/>
                        <Badge label="Total" value={this.state.total}/>
                        <Badge label="Average Win Time" value={this.state.avgWinTime}/>
                    </div>
                </div>
                <Table title="Moves" columns="Guess,Message" values={this.state.moves} properties="guess,message"/>
            </div>
        );
    }
}

export default Game;

Move.js:

class Move {
    constructor(guess, message) {
        this.guess = guess;
        this.message = message;
    };
}

export default Move;

Button.js:

import React, {Component} from 'react';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

class Button extends Component {
    render() {
        return (
            <div className="form-group">
                <button ref={ (btn) => {this.btn = btn;} }
                        onClick={this.props.doClick}
                        className="btn btn-success">{this.props.label}
                </button>
            </div>
        )
    }
}

export default Button;

Alert.js:
import React, {Component} from 'react';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

class Alert extends Component {

    render() {
        return (
            <div> {this.props.valid &&
            <div className="alert alert-danger" role="alert">
                {this.props.message}
            </div>
            } </div>
        )
    }
}

export default Alert;

Badge.js:
import React, {Component} from 'react';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

class Badge extends Component {

    render() {
        return (
            <div className="form-group">
                <label htmlFor={this.props.value}>{this.props.label}</label>
                <span id={this.props.value} className="badge">
                    {this.props.value}
                </span>
            </div>
        )
    }
}

export default Badge;

Table.js:
import React, {Component} from 'react';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';

class Table extends Component {

    render() {
        return (
            <div className="panel panel-success" data-bind="visible: moves().length > 0">
                <div className="panel-heading">
                    <h3 className="panel-title">{this.props.title}</h3>
                </div>
                <div className="panel-body">
                    <table className="table-responsive table table-striped">
                        <thead>
                        <tr>
                            {this.props.columns.split(',').map(
                                (col, index) =>
                                    <th key={index}>{col}</th>
                            )
                            }
                        </tr>
                        </thead>
                        <tbody>
                        {this.props.values.map(
                            (val, i1) =>
                                <tr key={i1}>
                                    {this.props.properties.split(",").map(
                                        (p, i2) =>
                                            <th key={i2}>{val[p]}</th>
                                    )
                                    }
                                </tr>
                        )
                        }
                        </tbody>
                    </table>
                </div>
            </div>
        );
    }
}

export default Table;

You can download the React implementation of the game using this link.

Angular 2 Implementation

app.module.ts:
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule} from '@angular/forms';
import {NgModule} from '@angular/core';

import {AppComponent} from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule, FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

app.component.ts:
import {Component, OnInit} from '@angular/core';
import {Move} from './move';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  private title: string;
  private guess: number;
  private tries: number;
  private secret: number;
  private gameLevel: number;
  private moves: Move[];

  constructor() {
    this.title = 'Mastermind!';
    this.guess = 123;
    this.tries = 0;
    this.secret = 0;
    this.gameLevel = 3;
    this.moves = [];
  }

  ngOnInit(): void {
    this.secret = this.createSecret();
    console.log(this.secret);
  }

  private createSecret(): number {
    const digits: number[] = [];
    digits.push(this.createDigit(1, 9));
    for (let i = 1; i < this.gameLevel; ++i) {
      let candidate = 0;
      do {
        candidate = this.createDigit(0, 9);
      } while (digits.indexOf(candidate) >= 0);
      digits.push(candidate);
    }
    let value = 0;
    for (let i = 0; i < digits.length; ++i) {
      value = 10 * value + digits[i];
    }
    return value;
  }

  private createDigit(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  public play() {
    if (this.secret === this.guess) {
      this.gameLevel++;
      this.tries = 0;
      this.moves.splice(0);
      this.moves.push(new Move(this.guess, 'You win!'));
      this.secret = this.createSecret();
    } else {
      this.tries++;
      const message: string = this.createMessage();
      this.moves.push(new Move(this.guess, message));
    }
  }

  private createMessage(): string {
    const strSecret: string = this.secret.toString();
    const strGuess: string = this.guess.toString();
    let perfectMatch = 0;
    let partialMatch = 0;
    for (let i = 0; i < strSecret.length; ++i) {
      for (let j = 0; j < strGuess.length; ++j) {
        if (strSecret.charAt(i) === strGuess.charAt(j)) {
          if (i === j) {
            ++perfectMatch;
          } else {
            ++partialMatch;
          }
        }
      }
    }
    if (perfectMatch === 0 && partialMatch === 0) {
      return 'No match';
    }
    let message = '';
    if (perfectMatch > 0) {
      message = '+' + perfectMatch;
    }
    if (partialMatch > 0) {
      message += '-' + partialMatch;
    }
    return message;
  }
}
app.component.html:
<script type="application/javascript" src="node_modules/jquery/dist/jquery.js"></script>
<script type="application/javascript" src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<p>
<div class="container" role="main">
  <div class="panel panel-primary">
    <div class="panel-heading">
      <h3 class="panel-title">{{title}}</h3>
    </div>
    <div class="panel-body">
      <div class="form-group">
        <label for="gameLevel">Game Level</label>
        <span id="gameLevel">{{gameLevel}}</span>
      </div>
      <div class="form-group">
        <label for="tries">Tries</label>
        <span id="tries">{{tries}}</span>
      </div>
      <div class="form-group">
        <label for="guess">Guess</label>
        <input class="form-control" type="number" id="guess" [(ngModel)]="guess"/>
      </div>
      <div class="form-group">
        <button class="btn btn-success" (click)="play()">Play</button>
      </div>
    </div>
  </div>
  <ng-container *ngIf="moves.length>0">
    <div class="panel panel-primary">
      <div class="panel-heading">
        <h3 class="panel-title">Moves</h3>
      </div>
      <div class="panel-body">
        <table class="table table-striped">
          <thead>
          <tr>
            <th>No</th>
            <th>Guess</th>
            <th>Message</th>
          </tr>
          </thead>
          <tbody>
          <tr *ngFor="let move of moves ; let i= index">
            <td>{{i+1}}</td>
            <td>{{move.guess}}</td>
            <td>{{move.message}}</td>
          </tr>
          </tbody>
        </table>
      </div>
    </div>
  </ng-container>
</div>

You can download the Angular 4 implementation of the game using this link.

Vue JS Implementation

game.js:
class Move {
    constructor(guess, message) {
        this.guess = guess;
        this.message = message;
    }
};

Vue.component('utable', {
    props: ['items', 'properties', 'columns'],
    template:
    '<table class="table table-striped">'        + '<thead>'            + '<tr>'            + "<th v-for=\"column in columns\">{{column}}</th>"            + '</tr>'        + '</thead>'        + '<tbody>'            + '<tr v-for="item in items">'            + '<td v-for="prop in properties">{{item[prop]}}</td>'            + '</tr>'        + '</tbody>'    + '</table>'})

var app = new Vue({
    el: '#app',
    data: {
        moves: [],
        movesColumns: ["Guess", "Message"],
        movesProperties: ["guess", "message"],
        secret: 122,
        wins: 0,
        loses: 0,
        guess: 123,
        tries: 0,
        counter: 60    },
    computed: {
        total: function () {
            return this.loses + this.wins;
        }
    },
    created: function () {
        this.init();
        setInterval(() => {
            this.counter--;
            if (this.counter <= 0) {
                this.loses++;
                this.init(new Move(this.secret, "You lose!"));
            }
        }, 1000);
    },
    methods: {
        createSecret: function () {
            var numbers = [this.createRandomDigit(1, 9)];
            while (numbers.length < 3) {
                var candidate = this.createRandomDigit(0, 9);
                if (numbers.indexOf(candidate) === -1)
                    numbers.push(candidate);
            }
            return Number(numbers.join(''));
        },
        createRandomDigit: function (min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },
        init: function (move) {
            this.moves = [];
            if (move !== undefined && move !== null) {
                this.moves.push(move);
            }
            this.secret = this.createSecret();
            this.tries = 0;
            this.counter = 60;
        },
        evaluate: function (guess) {
            guess = Array.from(guess.toString());
            let secret = Array.from(this.secret.toString());
            let partialMatch = 0, perfectMatch = 0;
            for (let i in guess) {
                let index = secret.indexOf(guess[i]);
                if (index == -1) continue;
                if (index == i) {
                    perfectMatch++;
                } else {
                    partialMatch++;
                }
            }
            if (partialMatch == 0 && perfectMatch == 0) return "No match!";
            let message = "";
            if (perfectMatch > 0) message = "+" + perfectMatch;
            if (partialMatch > 0) message = message + "-" + partialMatch;
            return message;
        },
        play: function () {
            if (Number(this.guess) === Number(this.secret)) {
                this.wins++;
                this.init(new Move(this.guess, "You win!"));
                return;
            }
            this.tries++;
            if (this.tries > 10) {
                this.loses++;
                this.init(new Move(this.secret, "You lose!"));
                return;
            }
            let message = this.evaluate(this.guess);
            this.moves.push(new Move(this.guess, message));
        }
    }
});

index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Mastermind (vue)</title>
    <style type="text/css">
        @import url('node_modules/bootstrap/dist/css/bootstrap.css');
        @import url('node_modules/bootstrap/dist/css/bootstrap-theme.css');
    </style>
    <script type="application/javascript" src="node_modules/jquery/dist/jquery.js"></script>
    <script type="application/javascript" src="node_modules/bootstrap/dist/js/bootstrap.js"></script>
    <script type="application/javascript" src="node_modules/vue/dist/vue.js"></script>
</head>
<body>
<p/>
<div id="app" class="container" role="main">
    <div class="row col-md-4">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">Game Console</h3>
            </div>
            <div class="panel-body">
                <div>
                    <label for="guess">Guess:</label>
                    <input id="guess" v-model="guess" class="form-control" type="text"/>
                    <button id="playButton" v-on:click="play" class="btn btn-success">Play</button>
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">Game Statistics</h3>
                </div>
                <div class="panel-body">
                    <div class="form-group">
                        <label for="counter">Counter:</label>
                        <span id="counter">{{counter}}</span>
                    </div>
                    <div class="form-group">
                        <label for="tries">Tries:</label>
                        <span id="tries">{{tries}}</span>
                    </div>
                    <div class="form-group">
                        <label for="wins">Wins:</label>
                        <span id="wins">{{wins}}</span>
                    </div>
                    <div class="form-group">
                        <label for="loses">Loses:</label>
                        <span id="loses">{{loses}}</span>
                    </div>
                    <div class="form-group">
                        <label for="total">Total:</label>
                        <span id="total">{{total}}</span>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">Moves</h3>
                </div>
                <div class="panel-body">
                    <utable v-bind:items="moves" v-bind:columns="movesColumns" v-bind:properties="movesProperties"></utable>
                </div>
            </div>
        </div>
    </div>
</div>
<script type="application/javascript" src="js/game.js"></script>
</body>
</html>

You can download the Vue js implementation of the game using this link.