ahmad.mo74
سه شنبه 11 آذر 1393, 18:58 عصر
سلام،
من تو بخشی از پروژم یک search engine درست کردم که یک keyword میگیره بر اساس اون یه سری پارامترهای دیگه در نظر گرفته میشه و ... سرچ رو انجام میده و یک خروجی json هم برمیگردونه.
اما اگر چند کاربر به طور همزمان یک keyword رو سرچ کنن فرایند سرچ که خیلی هم هزینه داره برای همشون جداگانه انجام میشه، از طرفی ساختار دیتابیسم جوریه که هر keyword فقط یکبار سرچ بشه کافیه و بر اساس سرچ هایی که در آینده انجام میشه (یا قبلا شده) ممکنه به keyword های دیگه هم مرتبط بشه و یا جواب اونا رو کامل تر کنه (مثلا کلمه real madrid و ronaldo میتونن به هم مرتبط بشن و جواب های هر کدوم تکمیل کننده هم باشه)
بخشی از کد :
public String search(Map<String, Object> params) {
String result = /*search*/;
return result;
}
حالا برای جلوگیری از این مسئله 3 تا راه هست :
1- استفاده از synchronized : به دلایلی که میدونید در اینجا استفاده از synchronized کاملا مشکل رو حل نمیکنه، چون هم سرعت رو به شدت پایین میاره و هم اینکه به هر حال برای هر request یکبار فرایند سرچ انجام میشه اما جلوی اینکه یک keyword دوبار تو دیتابیس ذخیره بشه رو میگیره (اگر قبلا ذخیره شده باشه از همون استفاده میشه)
2- استفاده از ReentrantLock : اینجا یه قدم جلوتر میریم در مقایسه با استفاده از synchronized ، با این برتری که میشه فقط سرچ های مشابه رو sync کرد نه همه رو :
private static final Map<Object, ReentrantLock> CONCURRENT_SEARCHES = new ConcurrentHashMap<>();
public String search(Map<String, Object> params) {
Object key = params.get("keyword");
assert key != null;
ReentrantLock lock = CONCURRENT_SEARCHES.get(key);
if (lock == null) {
CONCURRENT_SEARCHES.put(key, (lock = new ReentrantLock()));
}
lock.lock();
try {
String result = /*search*/;
return result;
} finally {
lock.unlock();
}
}
3- استفاده از Shared Objects : تقریبا بهترین راه بود که به ذهنم رسید :
private static final Map<Object, Queue<Locker<Object, String>>> CONCURRENT_SEARCHES = new ConcurrentHashMap<>();
public String search(Map<String, Object> params) {
Object key = params.get("keyword");
assert key != null;
Queue<Locker<Object, String>> queue = CONCURRENT_SEARCHES.get(key);
if (queue != null) {
System.out.println("waiting");
Locker<Object, String> locker = new Locker<>(key);
queue.add(locker);
locker.lock();
String result = locker.getValue();
return result == null ? "[]" : result;
}
System.out.println("new search");
CONCURRENT_SEARCHES.put(key, (queue = new ConcurrentLinkedQueue<>()));
String result = /*search*/;
CONCURRENT_SEARCHES.remove(key);
Locker<Object, String> locker;
while ((locker = queue.poll()) != null) {
locker.setValue(result);
locker.unlock();
}
return result;
}
public final class Locker<K, V> {
private final Object o = new Object();
private final K key;
private V value;
public Locker(K key) {
this.key = key;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public void lock() {
synchronized (o) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void lock(long timeout) {
synchronized (o) {
try {
o.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void unlock() {
synchronized (o) {
o.notify();
}
}
}
این روش خیلی خوبه چون اگر 100 تا سرچ همزمان با یک keyword هم داشته باشیم فقط یکبار سرچ انجام میشه و همون جواب به همشون داده میشه، اما تست کردم و به دلایلی که نمیدونم این خروجی رو داد :
public class Test {
public static void main(String[] args) throws InterruptedException {
String id = "1";
ProcessSession session = ProcessManager.openSession(id);
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(() -> session.search("keyword"));
}
}
}
خروجی :
new search
waiting
new search
new search
new search
waiting
new search
waiting
waiting
waiting
دوستان اگر مشکلشو میدونید راهنمایی کنید چون هر جوری فکر میکنم نباید این اتفاق بیفته! اگر هم راه بهتری رو بلدید پیشنهاد کنید، ممنون :)
پ.ن : به دلایلی که بالا توضیح دادم نمیتونم جواب سرچ رو کش کنم چون جواب ها ممکنه تغییر کنه به مرور زمان!
من تو بخشی از پروژم یک search engine درست کردم که یک keyword میگیره بر اساس اون یه سری پارامترهای دیگه در نظر گرفته میشه و ... سرچ رو انجام میده و یک خروجی json هم برمیگردونه.
اما اگر چند کاربر به طور همزمان یک keyword رو سرچ کنن فرایند سرچ که خیلی هم هزینه داره برای همشون جداگانه انجام میشه، از طرفی ساختار دیتابیسم جوریه که هر keyword فقط یکبار سرچ بشه کافیه و بر اساس سرچ هایی که در آینده انجام میشه (یا قبلا شده) ممکنه به keyword های دیگه هم مرتبط بشه و یا جواب اونا رو کامل تر کنه (مثلا کلمه real madrid و ronaldo میتونن به هم مرتبط بشن و جواب های هر کدوم تکمیل کننده هم باشه)
بخشی از کد :
public String search(Map<String, Object> params) {
String result = /*search*/;
return result;
}
حالا برای جلوگیری از این مسئله 3 تا راه هست :
1- استفاده از synchronized : به دلایلی که میدونید در اینجا استفاده از synchronized کاملا مشکل رو حل نمیکنه، چون هم سرعت رو به شدت پایین میاره و هم اینکه به هر حال برای هر request یکبار فرایند سرچ انجام میشه اما جلوی اینکه یک keyword دوبار تو دیتابیس ذخیره بشه رو میگیره (اگر قبلا ذخیره شده باشه از همون استفاده میشه)
2- استفاده از ReentrantLock : اینجا یه قدم جلوتر میریم در مقایسه با استفاده از synchronized ، با این برتری که میشه فقط سرچ های مشابه رو sync کرد نه همه رو :
private static final Map<Object, ReentrantLock> CONCURRENT_SEARCHES = new ConcurrentHashMap<>();
public String search(Map<String, Object> params) {
Object key = params.get("keyword");
assert key != null;
ReentrantLock lock = CONCURRENT_SEARCHES.get(key);
if (lock == null) {
CONCURRENT_SEARCHES.put(key, (lock = new ReentrantLock()));
}
lock.lock();
try {
String result = /*search*/;
return result;
} finally {
lock.unlock();
}
}
3- استفاده از Shared Objects : تقریبا بهترین راه بود که به ذهنم رسید :
private static final Map<Object, Queue<Locker<Object, String>>> CONCURRENT_SEARCHES = new ConcurrentHashMap<>();
public String search(Map<String, Object> params) {
Object key = params.get("keyword");
assert key != null;
Queue<Locker<Object, String>> queue = CONCURRENT_SEARCHES.get(key);
if (queue != null) {
System.out.println("waiting");
Locker<Object, String> locker = new Locker<>(key);
queue.add(locker);
locker.lock();
String result = locker.getValue();
return result == null ? "[]" : result;
}
System.out.println("new search");
CONCURRENT_SEARCHES.put(key, (queue = new ConcurrentLinkedQueue<>()));
String result = /*search*/;
CONCURRENT_SEARCHES.remove(key);
Locker<Object, String> locker;
while ((locker = queue.poll()) != null) {
locker.setValue(result);
locker.unlock();
}
return result;
}
public final class Locker<K, V> {
private final Object o = new Object();
private final K key;
private V value;
public Locker(K key) {
this.key = key;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public void lock() {
synchronized (o) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void lock(long timeout) {
synchronized (o) {
try {
o.wait(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void unlock() {
synchronized (o) {
o.notify();
}
}
}
این روش خیلی خوبه چون اگر 100 تا سرچ همزمان با یک keyword هم داشته باشیم فقط یکبار سرچ انجام میشه و همون جواب به همشون داده میشه، اما تست کردم و به دلایلی که نمیدونم این خروجی رو داد :
public class Test {
public static void main(String[] args) throws InterruptedException {
String id = "1";
ProcessSession session = ProcessManager.openSession(id);
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(() -> session.search("keyword"));
}
}
}
خروجی :
new search
waiting
new search
new search
new search
waiting
new search
waiting
waiting
waiting
دوستان اگر مشکلشو میدونید راهنمایی کنید چون هر جوری فکر میکنم نباید این اتفاق بیفته! اگر هم راه بهتری رو بلدید پیشنهاد کنید، ممنون :)
پ.ن : به دلایلی که بالا توضیح دادم نمیتونم جواب سرچ رو کش کنم چون جواب ها ممکنه تغییر کنه به مرور زمان!