‫استفاده از مکانیزم Cache در داخل جاوا با استفاده از کتابخانه ی Ehcache

پیشگفتار

‫ابتدا به تعریف cache می پردازیم: cache در حقیقت حافظه ای می باشد که اطلاعات بخصوصی را در خود جای داده است و میتواند به سیستم های کامپیوتری کمک کند تا بجای درخواست های متوالی بر روی سرورها با سرعت بالا دیتارو از این حافظه واکشی و به یوزر برگردانند.

‫استفاده از مکانیزم caching یکی از پرکاربردترین مکانیزم های موجود برای پایین آوردن میزان بار بر روی سیستم های کامپیوتری محسوب میشود. امروزه وبسایت های پر بازدید دنیا برای کنترل بار و ترافیک روی سرورهای مختلفشان من جمله دیتابیس، وب سرور و … از این مکانیزم استفاده میکنند تا سهم عمده ای از درخواست های مشابه بدون نیاز به درخواست های متوالی به سرورهای سرویس دهنده از طریق cache موجود انجام بپذیرد و نیاز به خریداری سرورهای خیلی بزرگ با قدرت بسیار بالا هم کمتر شود. در دنیای امروز که ارتباطات و درخواست ها بیش‫ از هر زمان دیگه ای موجود می باشد تکنولوژی  caching به یک مسائله مهم تبدیل شده که همه ی شرکت های سرویس  دهنده باید به آن توجه کنند. همچنین برای یوزرهای یک شرکت امروزه سرعت پاسخگویی یک امر ضروری محسوب میشود، لذا استفاده از cache  و پاسخ به درخواست های با لود بالا به یوزرها میتواند از طریق مکانیزم های متفاوت caching امکان  بپذیرد که بسیار کارامد است. استفاده از cache ها میتواند در سناریوهای متفاوتی مورد استفاده واقع شود :

vertical scalability‫۱-
‫۲- horizontal scalability
‫۳-in-process caching
‫۴- in-memory caching
‫۵- دیتابیس های in-memory
‫۶- دیتاگرید های in-memory

برای اطلاعات بیشتر در مورد هرکدام میتوانید به این لینک مراجعه کنید

کتابخانه های Cache در جاوا

‫کتابخانه های بسیاری من جمله Google Guava ، Infinispan و Ehcache برای مدریت cache در داخل جاوا وجود دارند ولی بطور کل کتابخانه ی Ehcache از همه ی کتابخانه ی دیگر  معروفتر است و بیشتر مورد استفاده قرار میگیرد. در این آموزش به کتابخانه ی Ehcache پرداخته می شود و کدی با این کتابخانه می نویسیم.

 

کتابخانه ی Ehcache

‫یک کتابخانه ی اوپن سورس معروف با ویژگی های متنوع است و بیشترین سهم استفاده را در ‫کنار رقبای خود دارد. در این آموزش سناریوی برنامه ما به این شکل است:

‫ما برنامه ای را توسعه میدهیم که برایمان پیشنهاد ویژه ی یک وبسایت را شبیه سازی کند. به این شکل که:

‫۱-پیشنهاد ویژه در داخل یک cache قرار میگیرد و به آن cache یک زمان معین برای وجود پیشنهاد ویژه داده میشود (دقیقا مثل سناریوی واقعی) مثل سایت های معروف خرید و فروش  که فرضا یک پیشنهاد ویژه ی ۱ روزه را قرار میدهند. یعنی ما با سرویس دهی از طریق cache توی یک بازه ی زمانی خاص، خاصیت پیشنهاد ویژه را شبیه سازی میکنیم!
‫۲- برای هر آیتم یک قیمت اولیه و تعداد موجود جنس در نظر گرفته می شود  و اگر این آیتم در تایم پیشنهاد ویژه (تایم valid بودن cache) قرار داشت، درصد تخفیفی که ما تعیین میکنیم بر روی آن اعمال میشود.
‫۳- یوزرهای مختلف با استفاده از یک مجموعه ترد شبیه سازی می شوند و شروع به خریداری اجناس میکنند.
‫۴-  اگر تایم خرید اجناس (تایم cache عه پیشنهاد ویژه جنس) تمام شده بود، یوزرها با قیمت واقعی اون کالا اون جنس را می خرند.
‫۵- اگر هم که جنس به پایان رسیده بود، به یوزره درخواست کننده پیامی داده میشود که جنس مورد نظر تمام شده است.

نوشتن برنامه ی استفاده از کتابخانه EhCache

package com.iropensource.cahce;

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;

import java.time.Duration;
import java.util.Map;

public class EhCacheSingleton {

    //یه آبجکت مدیریت کننده کش برام بساز
    private static CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
    private static EhCacheSingleton instance;

    private Cache<Long, Double> cache;

    /**
     * بحالت سینگلتن برام این کش رو بساز و بهش زمان اکتیو بودن اختصاص بده
     * @param discounts به ازای آی دی هر جنس چند درصد تخفیف باید در نظر گرفته شود
     * @param duration زمانی که تخفیف را میشود استفاده کرد
     */
    private EhCacheSingleton(final Map<Long, Double> discounts, final Duration duration) {
        cache = cacheManager.createCache("productOfferCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, Double.class,
                        ResourcePoolsBuilder.heap(100))
                        .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(duration))
                        .withEvictionAdvisor((aLong, aDouble) -> true) // وقتی زمان تخفیف به پایان رسید از تو کش بندازشون بیرون
                        .build());

        discounts.forEach((productId, discountPercentage) -> cache.put(productId, discountPercentage));
    }

    /**
     * یه پیشنهاد ویژه ی جدید بهم بده
     * @param discounts
     * @param duration
     * @return
     */
    public static EhCacheSingleton newOffer(final Map<Long, Double> discounts, final Duration duration) {
        if (instance == null) {
            synchronized (EhCacheSingleton.class) {
                if (instance == null) {
                    instance = new EhCacheSingleton(discounts, duration);
                }
            }
        }

        return instance;
    }

    /**
     * حتی اگر پیشنهاد ویژه ای موجود هستش بازم یه اینستنس جدید با cache عه جدید برام درست کن
     * @param discounts
     * @param duration
     * @return
     */
    public static EhCacheSingleton forceRefresh(final Map<Long, Double> discounts, final Duration duration) {

        synchronized (EhCacheSingleton.class) {
            instance = new EhCacheSingleton(discounts, duration);
        }

        return instance;
    }

    public Cache<Long, Double> getCache() {
        return cache;
    }
}

package com.iropensource.model;

public class Product {

    private Long id;
    private String name;
    private Double price;
    private int availability;


    public Product(Long id, String name, Double price, int availability) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.availability = availability;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Double getPrice() {
        return price;
    }

    public int getAvailability() {
        return availability;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public void setAvailability(int availability) {
        this.availability = availability;
    }
}

package com.iropensource.simulation;

import com.iropensource.model.Product;
import org.ehcache.Cache;

import java.util.List;
import java.util.Optional;

public class Processor {

    private Cache<Long, Double> cache;
    private List<Product> products;

    public Processor(Cache<Long, Double> cache, List<Product> products) {
        this.cache = cache;
        this.products = products;
    }

    public void sell(Long productId) {

        synchronized (products) {
            Optional<Product> op = products.stream().filter(product -> product.getId() == productId).findFirst();
            if (!op.isPresent())
                return;

            Product product = op.get();
            String productName = product.getName();
            System.out.println(String.format("مشتری %s :", Thread.currentThread().getName()));

            if (product.getAvailability() < 1) {
                System.out.format("متاسفانه دیگه محصول %s بطور کامل به فروش رسیده\n\n", productName);
                return;
            }


            Double discount = cache.get(productId);
            Double actualPrice = product.getPrice();
            int remaining = product.getAvailability();

            if (discount != null) {
                Double discountedPrice = computeDiscountedPrice(actualPrice, discount);
                System.out.format("شما موفق به خریداری آیتم %s در پیشنهاد ویژه ی ما شدین. قیمت فروش : %s -- قیمت واقعی جنس : %s -- تعداد باقی مانده %d\n\n", productName, String.valueOf(discountedPrice), String.valueOf(actualPrice), remaining);

            } else {
                System.out.format("متاسفانه پیشنهاد ویژه ی فروش آیتم %s به پایان رسیده و شما آن را با قیمت واقعی %s خریداری کردید -- تعداد باقی مانده %d\n\n", productName, String.valueOf(actualPrice), remaining);
            }

            product.setAvailability(product.getAvailability() - 1);

            // ۲ ثانیه بعد از هر فروش صبر کن
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private Double computeDiscountedPrice(Double price, Double discountAmount) {
        price = price - (price * (discountAmount / 100));
        return price;
    }
}

package com.iropensource;

import com.iropensource.cahce.EhCacheSingleton;
import com.iropensource.model.Product;
import com.iropensource.simulation.Processor;
import org.ehcache.Cache;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Launcher {
    public static List<Product> products4Sales = new ArrayList<>();

    // تعداد مشتری ها
    public static final int CUSTOMERS = 100;
    //مدت زمان پیشنهاد ویژه به ثانیه
    public static final int DURATION_IN_SEC = 40;

    static {
        // لیست آیتم ها با شماره کالا، اسم، قیمت واقعی و تعداد موجود
        products4Sales.add(new Product(1L, "Shampoo", 10000.0, 5));
        products4Sales.add(new Product(2L, "Soap", 2000.0, 10));
        products4Sales.add(new Product(3L, "Toothpaste", 20000.0, 8));
        products4Sales.add(new Product(4L, "Perfume", 50000.0, 14));
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // لیست تخفیف را برای هر آیتم در نظر بگیر
        Map<Long, Double> discountList = new HashMap<Long, Double>() {{
            put(1L, 10.0);
            put(2L, 20.0);
            put(3L, 30.0);
            put(4L, 40.0);
        }};

        //به مدت ۲ دقیقه پیشنهاد ویژه
        EhCacheSingleton ehCacheSingleton = EhCacheSingleton.newOffer(discountList, Duration.ofSeconds(DURATION_IN_SEC));
        Cache<Long, Double> cache = ehCacheSingleton.getCache();


        Random random = new Random();

        // به تعداد مشتری هام برام ترد بساز - شبیه سازی
        ExecutorService executor = Executors.newFixedThreadPool(CUSTOMERS);
        List<Future<?>> list = new ArrayList<>();
        for (int i = 0; i < CUSTOMERS; i++) {
            Future<?> future = executor.submit(() -> {
                Processor proc = new Processor(cache, products4Sales);
                //  از آیتم با شماره ی ۰ تا ۴ رو برام رندوم بگیر و بفروش
                proc.sell(Long.valueOf(random.nextInt(products4Sales.size()+1)));
            });

            list.add(future);
        }

        for (Future<?> fut : list) {
            Object s = fut.get();
        }

        executor.shutdown();

    }
}

نمایی مختصر شده از خروجی برنامه با استفاده از ۱۰۰ تا ترد بعنوان ۱۰۰ تا یوزر 

مشتری pool-1-thread-1 :
شما موفق به خریداری آیتم Soap در پیشنهاد ویژه ی ما شدین. قیمت فروش : 1600.0 -- قیمت واقعی جنس : 2000.0 -- تعداد باقی مانده 10

مشتری pool-1-thread-100 :
شما موفق به خریداری آیتم Perfume در پیشنهاد ویژه ی ما شدین. قیمت فروش : 30000.0 -- قیمت واقعی جنس : 50000.0 -- تعداد باقی مانده 14

مشتری pool-1-thread-96 :
شما موفق به خریداری آیتم Shampoo در پیشنهاد ویژه ی ما شدین. قیمت فروش : 9000.0 -- قیمت واقعی جنس : 10000.0 -- تعداد باقی مانده 5

مشتری pool-1-thread-95 :
شما موفق به خریداری آیتم Toothpaste در پیشنهاد ویژه ی ما شدین. قیمت فروش : 14000.0 -- قیمت واقعی جنس : 20000.0 -- تعداد باقی مانده 8

مشتری pool-1-thread-94 :
شما موفق به خریداری آیتم Toothpaste در پیشنهاد ویژه ی ما شدین. قیمت فروش : 14000.0 -- قیمت واقعی جنس : 20000.0 -- تعداد باقی مانده 7


مشتری pool-1-thread-9 :
متاسفانه دیگه محصول Shampoo بطور کامل به فروش رسیده

مشتری pool-1-thread-7 :
متاسفانه دیگه محصول Perfume بطور کامل به فروش رسیده

مشتری pool-1-thread-6 :
متاسفانه دیگه محصول Toothpaste بطور کامل به فروش رسیده

مشتری pool-1-thread-5 :
متاسفانه دیگه محصول Soap بطور کامل به فروش رسیده

دانلود کل پروژه

Published by

mehdi

I'm a Passionate Software Developer and Team Leader