Showing posts with label multithreading. Show all posts
Showing posts with label multithreading. Show all posts

Wednesday, June 15, 2016

Functional Reactive Programming

Saya mengenal konsep Functional reactive programming (selanjutnya akan saya tulis FRP) di pertengahan tahun ini, mungkin bisa dibilang telat, tapi lebih baik terlambat daripada tidak sama sekali kan? :D


Dalam hemat saya, FRP merupakan perspektif baru dalam menyelesaikan permasalahan-permasalahan programming di masa yang sekarang. Mengapa sekarang? Karena permasalahan programming di masa lalu dan masa sekarang berbeda. Jika permasalahan masa lalu adalah hardware dan internet, maka salah satu permasalahan sekarang adalah bagaimana mengatasi traffic yang sangat besar dalam satu website.
1*HAoewX140mJ8JnogRGPr5g.png

Dalam gambar tersebut bisa dilihat bahwa, facebook dan twitter memiliki jumlah user sangat banyak, hal ini jelas menjadi permasalahan penting yang harus dihadapi.


Prinsip Reactive
Secara singkat mengutip dari Bob reactive programming bisa di ibaratkan, applikasi reactive sebagai spread sheet, dan  variable-variable nya sebagai cell nya, jika salah satu cell berubah maka yang lain berubah. Yang sudah lama kenal konsep object oriented programming (oop) mungkin pernah denger observer pattern, konsepnya sama :)


Reactive programming sendiri dibangun berdasarkan 4 prisip, bisa dilihat dalam reactive manifesto.
1*DoL5xCZLLf35Z1xVWBRqjw.png


Gambar diatas menunjukan guidance dalam membangun applikasi dengan konsep reactive. Mengutip dari Kevin Webber :
  • Responsive application merupakan goal
  • Responsive application tidak mungkin terwujud tanpa resilient dan scalability
  • Message driven architecture merupakan pondasi dari scalable, resilient dan ultimate responsive sistem.

Responsive
A responsive system is quick to react to all users — under blue skies and grey skies — in order to ensure a consistently positive user experience. (Kevin Webber, 2014)


Sebuah sistem bisa dikatakan responsive jika sistem tersebut mampu merespon dengan cepat (baik sukses maupun error) terhadap segala kondisi user. Dalam kasus sukses maupun error, sistem harus konsisten terhadap user experience nya.
Singkat nya, ketika ada error process, user harus dihadapkan pada pilihan yang menyenangkan seperti ketika sukses process. Sebagai contoh mungkin, saat user merequest uber saat peak hour (yg notabene traffic nya sangat banyak) dan saat bukan peak hour, user harus mendapatkan response time yg sama.


Resilient
A resilient system applies proper design and architecture principles in order to ensure responsiveness under grey skies as well as blue. (Kevin Webber, 2014)


Resilient sistem harus menyediakan design dan arsitektur error handle yang benar. Dalam hal ini, yang dinaksud benar adalah, ketika terjadi error, maka experience user haruslah sama ketika mengalamin proses yang sukses.


Scalable
A scalable system is easily upgraded on demand in order to ensure responsiveness under various load conditions. (Kevin Webber, 2014)


Reactive mudah di scale up maupun di scale out karena pemodelan arsitektur nya yang mengimplementasi asynchronous, non-blocking, dan parallelism.


Message Driven
A message-driven architecture merupakan pondasi dari reactive applications. Sebuah message-driven application mungkin dapat berupa event-driven, actor-based, maupun kombinasi keduanya.
Gambar di bawah ini menjelaskan bahwa komunikasi dilakukan antar node (atau actor jika menggunakan Actor model) melalui message. Dan message ini menjadi trigger untuk menentukan langkah selanjutnya.


actors.png


Actor model bisa juga digambarkan seperti gambar di bawah ini. Coordinator, player1 dan player2 merupakan actor, coordinator bisa bertindak sebagai router, dan player sebagai worker.
actors2-fig1-100159427-orig.jpg
Sumber http://images.techhive.com/images/idge/imported/article/jvw/2009/03/actors2-fig1-100159427-orig.jpg

Terkait implementasinya mungkin bisa ditilik disini. Dalam source code tersebut menggunakan akka, namun intinya adalah pemodelan nya. Dimana paradigma arsitektur nya bukan lagi MVC, namun reactive.

Kesimpulan :
Scalability dan resilient merupakan prioritas utama dalam memenuhi ekspektasi user, untuk itu diperlukan paradigma reactive programming dalam membangun arsitektur sistem.


Happy coding dan reactive :)


Sumber :


Friday, September 18, 2015

menguji atomisitas redis - INCR command

post ini ditujukan untuk menguji sifat atomisitas command INCR redis.

kebutuhannya adalah, misalnya di aplikasi saya membutuhkan sebuah generator kode unik. cara termudah untuk membuat kode unik adalah dengan melakukan increment yang dimulai dari suatu angka, dan seterusnya. nantinya ini bisa dikombinasikan dengan character lain.

jika kita melihat dari 1 jvm, hal ini mudah saja, misalnya kita bisa buat sebuah variable static yang menampung angka tertentu dan menyiapkan sebuah method synchronized bagi thread yang hendak mengambil value tersebut (dan meng-increment-nya).

kira-kira seperti ini gambarannya


namun hal ini akan menjadi masalah saat aplikasi kita semakin besar dan membutuhkan scaling ke lebih dari 1 jvm. karena static variable dan synchronized hanya berlaku di 1 jvm saja. anggaplah kita punya 2 jvm, jika menggunakan static variable dan synchronized method, maka masing-masing jvm akhirnya akan menyimpan angka incrementnya sendiri-sendiri.



sehingga jika digabungkan, misalnya kita hendak menyimpan value itu dalam sebuah database yang sama dimana field tersebut dibuat unique, besar kemungkinannya kita akan kena ConstraintViolationException.

lalu bagaimana solusinya?

idenya adalah kedua jvm tersebut harus mengakses memory yang sama. sebuah cache bisa kita manfaatkan, mari kita coba dengan redis. kita letakkan instance redis di sebuah tempat, bisa di vm yang sama atau berbeda, untuk pengujian ini semuanya berada di 1 vm yang sama. tetapi untuk production jelas sebaiknya masing-masing memiliki vm-nya sendiri-sendiri.

kira-kira seperti ini skema umumnya


dalam percobaan ini, kita akan menggunakan command INCR milik redis,
"The counter pattern is the most obvious thing you can do with Redis atomic increment operations. The idea is simply send an INCR command to Redis every time an operation occurs." -http://redis.io/commands/INCR

command ini bersifat atomic, sehingga aman untuk proses multithread.

skema ini yang akan saya coba


saya akan aktifkan 1 jvm dulu, berkomunikasi dengan redis, memanfaatkan command INCR untuk mendapatkan sebuah value, dan value tersebut akan saya simpan di database. penyimpanan di database ini hanya bertujuan untuk mempermudah pengecekan, nantinya saya akan cek dengan menggunakan query

 select * from (  
     select value, count(1) n from redis_result group by value  
 ) a where a.n>1;  

jika INCR berjalan dengan benar, maka query tersebut akan menghasilkan result kosong.

saya menggunakan springboot 1.2.5 dengan library spring data, orm hibernate, database connection pool menggunakan apache commons-dbcp 1.4, redis connection pool dengan jedis 2.5.2.

skenarionya, saya akan menyiapkan 1 controller untuk memudahkan akses (tadinya saya mau menggunakan junit tapi junit tidak memungkinkan untuk test multithread). controller tersebut akan memanggil suatu class service yang akan mengeksekusi 5 thread. di dalam masing-masing thread tersebut saya akan melakukan looping sebanyak 2000 iterasi yang akan memanggil command INCR ke redis dan menyimpan value-nya ke database (table redis_result).

dengan skenario ini, maka akan ada sebanyak 5*2000 record yang tersimpan di table.

berikut ini configurasi redis connection pool nya:
 <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">  
     <property name="testOnBorrow" value="true"/>  
     <property name="testOnReturn" value="true"/>  
     <property name="testWhileIdle" value="true"/>  
     <property name="numTestsPerEvictionRun" value="10"/>  
     <property name="maxTotal" value="5000" />  
     <property name="minIdle" value="100" />  
   </bean>  
   <!-- for pooling the publisher -->  
   <bean id="redisConnectionPool" class="redis.clients.jedis.JedisPool" destroy-method="destroy">  
     <constructor-arg index="0" ref="jedisPoolConfig"/>  
     <constructor-arg index="1" value="localhost" type="java.lang.String"/>  
     <constructor-arg index="2" value="6379" type="int"/>  
   </bean>  

configurasi db connection pool:
 <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">  
     <property name="driverClassName" value="${database.driverClassName}"/>  
     <property name="url" value="${database.url}"/>  
     <property name="username" value="${database.username}"/>  
     <property name="password" value="${database.password}"/>  
     <<property name="validationQuery" value="SELECT version();"/>  
     <property name="initialSize" value="100"/>  
     <property name="maxActive" value="200"/>  
     <property name="maxIdle" value="100">  
   </bean>  

RedisUtil.java, yaitu class yang berfungsi sebagai redis client
 @Component  
 public class RedisUtil {  

   private static final Logger LOGGER = LoggerFactory.getLogger(RedisUtil.class);  

   @Autowired  
   @Qualifier(value = "redisConnectionPool")  
   JedisPool redisConnectionPool;  

   private void releaseJedisConnection(Jedis jedis){  
     if(null != jedis) {  
       LOGGER.debug("Return redis connection to pool");  
       redisConnectionPool.returnResource(jedis);  
     }  
   }  

   /**  
    * INCR key  
    * @param key  
    * @return  
    */  
   public Long increment(String key){  
        Long result = null;  
        Jedis jedis = null;  
     try {  
          jedis = redisConnectionPool.getResource();  
       result = jedis.incr(key);  
     } catch (JedisConnectionException jce) {  
          LOGGER.error("Failed add set to {} :: {}", key, jce.getMessage());  
     } finally {  
          releaseJedisConnection(jedis);  
     }  
     return result;  
   }  

   /**  
    * SET key  
    * @param key  
    * @return  
    */  
   public void set(String key, String value){  
        Jedis jedis = null;  
     try {  
          jedis = redisConnectionPool.getResource();  
       jedis.set(key, value);  
     } catch (JedisConnectionException jce) {  
          LOGGER.error("Failed add set to {} :: {}", key, jce.getMessage());  
     } finally {  
          releaseJedisConnection(jedis);  
     }  
   }  
 }  

RedisTask.java, yaitu class yang meng-increment key di redis dan menyimpan di database (dalam skema di atas, ini sama dengan thread01, thread02, dst..)
 public class RedisTask implements Runnable {  
        
      RedisUtil redisUtil;  
      RedisResultRepository redisResultRepository;  
        
      private static final String INCR_KEY = "incrKey";  
        
      public RedisTask(){  
           redisUtil = ApplicationContextHolder.getApplicationContext().getBean(RedisUtil.class);  
           redisResultRepository = (RedisResultRepository) ApplicationContextHolder.getApplicationContext().getBean("redisResultRepository");  
      }  
        
      public String getIncrValue() {  
           String result = null;  
   
           Long val = redisUtil.increment(INCR_KEY);  
           if (val == null || val.intValue() == 0) {  
                redisUtil.set(INCR_KEY, 1 + "");  
                result = getIncrValue();  
           } else {  
                result = val.toString();  
           }  
   
           return result;  
      }  
   
      @Override  
      public void run() {  
           for(int i=0; i<2000; i++){  
                String value = getIncrValue();  
                RedisResult result = new RedisResult(value);  
                redisResultRepository.save(result);  
           }  
      }  
        
 }  

RedisDemoService.java, yaitu class yang akan mengeksekusi 5 instance RedisTask secara concurrent
 @Service  
 public class RedisDemoService {  
        
      private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(5);  
        
      public void redisIncrTest(){  
             
           for(int i=0; i<5; i++){  
                System.out.println("execute redis task " + i);  
                EXECUTOR.execute(new RedisTask());  
           }  
             
      }  
        
 }  

RedisResult.java, yaitu POJO model domainnya
 @Entity  
 public class RedisResult extends BaseEntity {  
        
      private static final long serialVersionUID = -4902968469441816467L;  
        
      @Column(name = "value")  
      private String value;  
        
      public RedisResult(){  
             
      }  
        
      public RedisResult(String value){  
           this.value = value;  
      }  
   
      public String getValue() {  
           return value;  
      }  
   
      public void setValue(String value) {  
           this.value = value;  
      }  
 }  

RedisResultRepository.java, yaitu interface dari springdata yang akan menjadi akses komunikasi ke db
 @Repository  
 public interface RedisResultRepository extends JpaRepository<RedisResult, Long> {  
   
 }  

dan yang terakhir adalah controller-nya untuk kita akses dengan REST, RedisDemoController.java
 @RestController  
 @RequestMapping(value = "/redis")  
 public class RedisDemoController {  
        
      @Autowired  
      private RedisDemoService redisDemoService;  
   
      @RequestMapping(value = "/demo", method = RequestMethod.GET)  
      @ResponseBody  
      public boolean calculate() throws Exception {  
   
           redisDemoService.redisIncrTest();  
   
           return true;  
      }  
        
 }  

jika aplikasi kita nyalakan pada port 8181, kemudian kita bisa akses dia dengan
 curl -XGET http://localhost:8181/redis/demo  

setelah prosesnya selesai, kita count apakah jumlahnya benar

lalu kita cek apakah ada duplikasi value


oke, aman. sejauh ini tidak ada masalah.
lalu kita harus coba dengan menggunakan 2 jvm, saya menggunakan code yang sama tetapi saya nyalakan pada port yang berbeda, aplikasi kedua ini menggunakan port 8182.

kita coba secara bersama-sama
 curl -XGET http://localhost:8181/redis/demo  
 curl -XGET http://localhost:8182/redis/demo  

kemudian kita cek apakah jumlah recordnya benar


apakah ada duplikasi


woohoo, hasilnya baik-baik saja.

jadi kesimpulannya adalah command INCR redis dapat digunakan untuk kasus seperti di atas. untuk lebih meyakinkan diri mungkin perlu dilakukan lagi dengan 10 atau lebih jvm, tentunya jika memiliki environment yang sesuai.

thanks.