Closed olove closed 5 years ago
『Cache的问题』 与 『TTL的传递问题』是正交的。
表述如下: @olove
在设计上/思路/概念上,
cache_context
/cache上下文TransmittableThreadLocal
,里面持有的是Cache对象(要传递的对象)TransmittableThreadLocal
中传递,所以这个Cache是Session级的。cache
维护的cache。Cache
的值(在下面的Demo代码中是Item
)。ThreadLocal
(包括InheritableThreadLocal
、TransmittableThreadLocal
)的中维护的值是lazy-init
的,所以在主线程中,要先主动get
一下以init
。
lazy-init
如果不去get
,ThreadLocal
值还不存在,自然也就不会被传递。get
一下以init
』是 示意地 封装在了BizService
的构造函数中。Session
的Cache
新建,要有对应/对称的Session
级Cache
的清理,否则会有
对应你的Demo代码,调整后,用代码表述如下:
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import io.reactivex.Flowable;
import io.reactivex.functions.Consumer;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
public class SessionCacheDemo {
@BeforeClass
public static void beforeClass() {
// RxJava 集成 TTL
RxJavaPlugins.setScheduleHandler(TtlRunnable::get);
}
@Test
public void getSomethingByCache() throws Exception {
BizService bizService = new BizService();
final Consumer<Object> printer = result -> System.out.printf("[%30s]: %s%n", Thread.currentThread().getName(), bizService.getCache());
Flowable.just(bizService)
.observeOn(Schedulers.io())
.map(BizService::getItemByCache)
.doOnNext(printer)
.blockingSubscribe(printer);
// 业务 在后续时刻 需要用到
Object object = bizService.getItemByCache();
printer.accept(object);
}
/**
* Mock Service
*/
private static class BizService {
private static final String ONLY_KEY = "ONLY_KEY";
private final TransmittableThreadLocal<ConcurrentMap<String, Item>> cache_context = new TransmittableThreadLocal<ConcurrentMap<String, Item>>() {
@Override
protected ConcurrentMap<String, Item> initialValue() {
return new ConcurrentHashMap<>(); // init cache
}
};
public BizService() {
// NOTE: AVOID cache object lazy init
cache_context.get();
}
public Item getItem() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// do nothing
}
return new Item(ThreadLocalRandom.current().nextInt(0, 10_000));
}
/**
* 获取业务数据,一般使用spring cache做缓存,这里简单实现
*/
public Item getItemByCache() {
final ConcurrentMap<String, Item> cache = cache_context.get();
return cache.computeIfAbsent(ONLY_KEY, key -> getItem());
}
public Item getCache() {
return cache_context.get().get(ONLY_KEY);
}
public void clearCache() {
cache_context.get().clear();
}
}
/**
* Mock Cache Data
*/
public static class Item {
private int id;
public Item(int id) {
this.id = id;
}
@Override
public String toString() {
return "Item{id=" + id + '}';
}
}
}
[ RxCachedThreadScheduler-1]: Item{id=248}
[ main]: Item{id=248}
[ main]: Item{id=248}
转成Demo
代码(SessionCacheDemo
)添加了到代码库中了 :
运行结果:
[ pool-1-thread-2] cache: Item(id=9335)
[ main] cache: Item(id=9335)
[ main] cache: Item(id=9335)
[ RxCachedThreadScheduler-1] cache: Item(id=8624)
[ main] cache: Item(id=8624)
[ main] cache: Item(id=8624)
感谢 @olove 场景说明 与 示例(代码)提供! ❤️
谢谢@oldratlee 这么详细的说明和demo用例
场景
某些业务流程计算逻辑复杂,基础数据读取服务可能需要多次调用。
希望做线程级缓存(更准确地说,因为涉及多个上下游线程,其实是Session级的缓存),避免重复调用外部服务。
问题
数据产生可能来源于子线程,
TransmittableThreadLocal
在copy
数据的时候,忽略主线程不存在的key
,导致主线程无法读取到子线程新创建的ThreadLocal
值。解决方案
TransmittableThreadLocal
可以增加参数控制是否需要向父线程传递新增ThreadLocal
。示例代码
The results: