6

I am sending prices to customers (10000+) but below code has loop that causes delays in the process for customers waiting for calculations.

PriceVisibleForCustomer = Price + CustomerMargin

Price - changing every 300ms - sent from central store, not related to customer instance

CustomerMargn - some plus or minus amount that is resulting from customer agreement/segment/administrator decision etc. It doesnt change during customer http session, I can keep it in memory

Customer - he takes part in the process after he logs in, he should see rapidly changing prices of 8 products.

Maybe I need some more technology ? I have Spring 3/4, Java, Weblogic and i could create even separate webapp for this task for providing calculated prices.

I thought about threads in Java but 10000+ customers would mean too many threads wouldnt it ? How to change this code? Maybe I should change architecture but how?

/**
     * Sends prices to customer. This method is called very often (300ms) as prices are changing in real time.
     * Customer should see prices also each 300ms
     * @param productId - id of a product that prices will be calculated
     * @param productIdToPriceMap 
     * @param customerIdToMarginMap - this map is changed every time customer logs in or logs out
     */
    private static void sendPricesToCustomers(Long productId,
            Map<Long, BigDecimal> productIdToPriceMap,
            Map<Long, BigDecimal> customerIdToMarginMap) {

        //This loop is blocking last customer from receiving price until all other customers wil have theri prices calculated. I could create threads, 10000+ customers will be logged in, I cant create so much threads... can I?
        for (Long customerId: customerIdToMarginMap.keySet()){
            BigDecimal customerMargin = customerIdToMarginMap.get(customerId);
            BigDecimal priceResult = productIdToPriceMap.get(productId).add(customerMargin);
            //send priceResult to websocket
        }

    }
  • 2
    You cant avoid that loop, since you need to perform the same task many times. And thats precisely what a loop is meant for. – fps Jul 18 '15 at 15:05
  • How are you notifying customers? Is it by sending json or XML through the network? Is it by means of a queue? – fps Jul 18 '15 at 15:08
  • 1
    To answer your question in the comments; creating as many threads as you have CPUs (double for hyperthreading) **can** improved performance. Creating more than that will degrade performance, 10000 threads would be a very bad idea – Richard Tingle Jul 18 '15 at 15:21
  • Federico: I am sending prices to webbrowser using websocket, but this is projecting stage, I can still change this. It could be HTTPStreaming but I don't see any difference. Richard: it seems that thread are not good idea. So what would be? Should I say to the company that is interested in the project that this requirements cannot be quaranteed? –  Jul 18 '15 at 15:26
  • Did you consider using parallelism in stream api? You can perform parallel().foreach(expression) and test its performance – PythaLye Jul 18 '15 at 15:41
  • Java 7 only unfortunatelly –  Jul 18 '15 at 15:51
  • Can a customer be a listener to the products he is interested? Each product would maintain a list of listeners that would be notified of a price change? – Constantin Jul 18 '15 at 16:23
  • Do all customers have to be notified at the same time, i.e. ALL of them every 300ms, or is it enough if EACH customer is notified every 300ms, no matter if another customer was notified i.e.100ms ago? – fps Jul 18 '15 at 16:40
  • Constantin *** Yes he could be a listener but the class / method that gets original prices from central store would still have to find all active listeners and send them those prices which means looping. On the other hand it could work if the central store api would have a method for subscribing many listeners, then every customer could connect separatelly. I could try making 10000+ connections with the central store API, but I dont know if it would be good idea... Frederico *** Not in the same time, just one shouldn't wait for other. –  Jul 18 '15 at 16:52
  • Guys I think Constanin make me think about putting JMS between central store API and customer. That would mean every customer that logs in would create a listener to JMS. Class connecting to Central store API would get new prices and send them to JMS. Then I wouldnt need looping. What do you think, would it work fast enough? What JMS could I use (Weblogic JMS, ActiveMQ, RabbitMQ, any for that purpose?) Would I be able to manage the jms listener after customer logs out? I should destroy it somehow. Is it ok to create 10000+ listeners to JMS? –  Jul 18 '15 at 16:59
  • The good thing about listener pattern is that you only need active listeners and you can control the number you need (keep it capped). Also, it is real-time notification, not polling. Just a thought – Constantin Jul 18 '15 at 17:01
  • The pure listener pattern in Java is not enough I guess - I would have to loop existing listeners and send them prices? Are you talking about JMS ? Considering JMS I am wondering about the message sequence. It should quarantee right message ordering so the old price would be send first .. Also if it would be ok that so many JMS listeners are created. –  Jul 18 '15 at 17:09
  • Do you really need so many active listeners at one time? (10000). That seems excessive. What is this for? The way I would do is keep a master listener, listening in each product in real-time, but publish the results out (make them available) not so often, to keep things manageable, but it is hard to say unless the specific details of your requirement are known – Constantin Jul 18 '15 at 17:13
  • I just read that creating JMS consumer means creating new Thread.. Requirements are in the question description. You log in and click a sheet where you should see dynamically changing data only for you (margin calculations). To be more specific these would not be prices but currency rates, that are calculated for you specifically (using margins and some parameters). –  Jul 18 '15 at 17:21
  • I also think of making the calculations in javascript i webbrowser and sending the same prices for all customers. The javascript would be aware of margins, they could be rendered in JSP page. Then some validation on the server side, so that customer couldnt change the calculations after he submit a form with this price. –  Jul 18 '15 at 19:39
  • More than 3 changes a second? How big are those changes? Sounds a VERY silly thing to do. – Fildor Jul 18 '15 at 21:12
  • Hi, what changes ? These are lets say prices of 20 products in json format that are changing very often and must be calculated for each customer before presentation. There 1000+ customers logged in and they will see these data. There are 3 changes pers second as you said. So how would you do that? How would you avoid this loop that slows the process? –  Jul 21 '15 at 18:34

2 Answers2

1

Here is a simple example of the Listener pattern, I am not sure if this approach will work for you but just throwing out some ideas ...

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

import javax.swing.Timer;

public class Demo {

  public static Product[] PRODUCTS = new Product[]{
      new Product("Computer", 400),
      new Product("Desk", 800),
      new Product("Chair", 70),
      new Product("Printer", 300),
      new Product("Television", 200)
  };

  public static void main(String[] args) throws InterruptedException {
      Customer john = new Customer("John", 3);    
      john.addProduct(PRODUCTS[1]);
      john.addProduct(PRODUCTS[2]);
      john.addProduct(PRODUCTS[3]);


      Customer mary = new Customer("Mary", 2);    
      mary.addProduct(PRODUCTS[1]);
      mary.addProduct(PRODUCTS[2]);
      mary.addProduct(PRODUCTS[4]);


      Thread.sleep(10000);
      System.exit(0);
  }
}

interface IPriceListener {
  public void priceChanged(Product product, int price);
}

class Customer implements IPriceListener {
  String _name;
  int _margin;
  Vector<Product> _products = new Vector<Product>();

  public Customer(String name, int margin){
    _name = name;
    _margin = margin;
  }

  public void addProduct(Product product){
    _products.add(product);
    product.addListener(this);
  }

  public void priceChanged(Product product, int price) {
    System.out.println("[" + _name + "][" + _products.get(_products.indexOf(product)).getName() + "][" + price + "][" + (price + _margin) + "]");
  }
}

class Product implements ActionListener {
  private int _startingPrice;
  private int _currentPrice;

  private String _name;
  private Timer _timer;
  private Vector<IPriceListener> _listeners = new Vector<IPriceListener>();

  public Product(String name, int price) {
    _name = name;
    _startingPrice = _currentPrice = price;
    _timer = new Timer(300, this);
    _timer.start();
  }

  public void addListener(IPriceListener listener) {
    _listeners.add(listener);
  }

  public void removeListener(IPriceListener listener){
    _listeners.remove(listener);
  }

  private void notifyListeners() {
    for(IPriceListener listener : _listeners){
      listener.priceChanged(this, getCurrentPrice());
    }
  }

  public void actionPerformed(ActionEvent e) {
    _currentPrice = _startingPrice + (int)(Math.random() * (5 - (-5))) + (-5);
    notifyListeners();
  }

  public final String getName() {
    return _name;
  }

  private synchronized final int getCurrentPrice() {
    return _currentPrice;
  }
}
Constantin
  • 1,506
  • 10
  • 16
  • Yes this is great pattern but it just moved the problem to method: notifyListeners, where you are looping 10000+ listeners. Lets talk about JMS for this task. –  Jul 18 '15 at 17:12
  • It is an interesting problem, I am also reading through here http://www.enterpriseintegrationpatterns.com/ObserverJmsExample.html to get some ideas .of how to apply this pattern with JMS for my own education .. – Constantin Jul 18 '15 at 17:33
0

One way to handle this is to create a single thread whose job is to consume priceResults off a queue and send them down the websocket (I'm assuming you have only one websocket). Your loop would then push priceResults onto the queue every 300ms without blocking the websocket thread. See ConcurrentLinkedQueue javadoc.

Edit: To avoid the delay between finishing the current loop through customerIdToMarginMap and beginning to loop through the next update, here are some options:

  1. Keep the queue concept and create a fixed thread pool where each thread pulls the next customerId/productIdToPriceMap/customerIdToMarginMapoff the queue. If you have four threads and 10,000 records, each thread will only have to process 2,500 records, thereby starting on the next 300ms data push 4 times earlier than your current implementation. Increase thread count as you find necessary for performance.

  2. Keep my original queue concept but change the way you receive the price updates. The reason you need to loop is because you're getting pricing updates for every customer at the same time. If you can instead e.g. create a threaded listener for a group of customers that receives a customerIdToMarginMap containing only customerIds it's meant to handle, the iteration time will be significantly decreased.

spork
  • 1,185
  • 1
  • 11
  • 17
  • That is good solution for me as for websocket api that would also cause delay so instead sending results to websocket in the loop (as in comment) I would send pricesResults to such queue and then some component could take them off and send to websocket. The problem is that the last push is after 300ms * 10000 - the last int in the loop. The queue also would have this problem as I would push priceResults onto the queue every 300ms but not all results at one time. The solution must avoid looping I think. –  Jul 18 '15 at 16:01
  • @user1308908: I see what you're saying. You will always have to loop at some point, but you can break the loop into several smaller loops via threading. See my updated answer. – spork Jul 18 '15 at 22:08