在最近的一次系统设计访谈中,我遇到了以下问题:
设计一个与缓存和DB接口的AppServer。
我想出了这个:
public class AppServer{
public Database DB;
public Cache cache;
public Value get(Key k){
Value res = cache.get(k);
if(res == null){
res = DB.get(k);
cache.set(k, res);
}
}
public void set(Key k, Value v){
cache.set(k, v);
DB.set(k, v);
}
}此代码很好,工作正常,但后续问题如下:
响应:
我认为我们只需要同步void set(Key k, Value v)中的2条集合行和Value get(Key k)中的1条集合方法,但是面试官也要求同步res = DB.get(k);。最后我同意他的意见,但不完全理解。线程不是有独立的堆栈和共享堆吗?因此,当线程执行get时,它将res存储在堆栈帧上的局部变量中,即使另一个线程连续执行get,前一个线程也保留其get值。然后,每个线程设置各自的获取值。
我想出了一个像Kafka这样的分布式队列解决方案,每次我们执行set / get命令时,我们都会对该命令排队,但他也提到了set是可以的,因为这个操作在缓存/ db中设置了一个值,但是如何返回get的正确值呢?有人能解释一下吗?
另外,版本控制系统和事件系统也有可能的解决方案?
将提出所有有洞察力的回应:)
发布于 2017-12-22 20:59:10
1
尽管JDBC“被认为”是线程安全的,但有些驱动程序并不是线程安全的,我将假设Cache也不是线程安全的(尽管大多数缓存应该是线程安全的),所以在这种情况下,您需要对代码进行以下更改:
get(...)方法set(...)方法假设没有其他方法访问上述字段,那么get(...)方法的行为取决于两件事:首先,可以看到来自set(...)方法的更新,其次,缓存丢失仅由单个线程存储。您需要同步,因为在缓存丢失的情况下,只有一个线程执行昂贵的DB查询。如果不同步整个get(...)方法,或者拆分同步语句,则另一个线程也可能在查找和插入之间看到缓存丢失。
我回答这个问题的方式是诚实地抛出整个问题。我会看看JCIP是如何编写缓存的,并以此为基础给出答案。
2
我认为你的队列解决方案很好。
我相信您的面试官的意思是,如果AppServer的另一个实例没有缓存已经是set(...)的内容,那么它将在DB中查找并找到正确的值。如果您使用多个线程,则此解决方案将是不正确的,因为两个线程可能存在冲突的值,那么缓存将有两个不同的值,而取决于您的DB的线程安全性,它甚至可能根本没有值。
理想情况下,您不会创建多个AppServer实例。
3.
我没有足够的经验来具体地评估这个问题,但是也许LRU缓存会在一定程度上提高性能,或者使用哈希环缓冲区。这可能有点牵强,但如果您想抛出它,甚至可以使用ML来确定最佳值,例如,在一天中的特定时间预加载以保留的值也可能有效。
如果缓存中总是缺少值,则无法改进代码。性能将取决于您的数据库。
https://stackoverflow.com/questions/47945244
复制相似问题