变通实现微服务的per request以提高IO效率(二)

*:first-child {
margin-top: 0 !important;
}

body>*:last-child {
margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
font-size: inherit;
}

h1 {
font-size: 28px;
color: #000;
}

h2 {
font-size: 24px;
border-bottom: 1px solid #ccc;
color: #000;
}

h3 {
font-size: 18px;
}

h4 {
font-size: 16px;
}

h5 {
font-size: 14px;
}

h6 {
color: #777;
font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
margin-top: 0;
padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
color: #4183C4;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
padding-left: 30px;
}

ul li > :first-child,
ol li > :first-child,
ul li ul:first-of-type,
ol li ol:first-of-type,
ul li ol:first-of-type,
ol li ul:first-of-type {
margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
margin-bottom: 0;
}

dl {
padding: 0;
}

dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}

dl dt:first-child {
padding: 0;
}

dl dt>:first-child {
margin-top: 0px;
}

dl dt>:last-child {
margin-bottom: 0px;
}

dl dd {
margin: 0 0 15px;
padding: 0 15px;
}

dl dd>:first-child {
margin-top: 0px;
}

dl dd>:last-child {
margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
margin: 0 0px;
padding: 0px 0px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px;
}

pre>code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent;
}

pre {
background-color: #f8f8f8;
border: 1px solid #ccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}

pre code, pre tt {
background-color: transparent;
border: none;
}

kbd {
-moz-border-bottom-colors: none;
-moz-border-left-colors: none;
-moz-border-right-colors: none;
-moz-border-top-colors: none;
background-color: #DDDDDD;
background-image: linear-gradient(#F1F1F1, #DDDDDD);
background-repeat: repeat-x;
border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
border-image: none;
border-radius: 2px 2px 2px 2px;
border-style: solid;
border-width: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
line-height: 10px;
padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}

blockquote>:first-child {
margin-top: 0px;
}

blockquote>:last-child {
margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
clear: both;
margin: 15px 0;
height: 0px;
overflow: hidden;
border: none;
background: transparent;
border-bottom: 4px solid #ddd;
padding: 0;
}

/* IMAGES
=============================================================================*/

img {
max-width: 100%
}
-->

效率

变通实现微服务的per request以提高IO效率中提到的同一请求过程中对于同一个方法的多次读取只实际调用一次,其余的读取均从第一次读取的结果缓存中获取,以提高读取的效率。实现方案是引入了Context对象,可以理解成上下文的一个环境变量,业务方法在获取数据时先从Context中取,如果取不到从数据库中加载并将结果写入Context中,而Context是通过ThreadLocal来存储。但实现有点复杂需要寻找优化方案。

Context方案的缺点

  • 复杂度增强,因为需要引入Context的特殊概念
  • 复用性比较低。需要在Context中为每种数据维护一个属性。比如存储用户,产品,价格等。当然也可以利用Map,这样会导致复杂度会更加强,在缓存清除的时候也会随着数据存储结构的复杂度提升而提升:之前是为每种数据定义一个ThreadLocal。
private ThreadLocal<CiaUserInfo> ciaUserInfoThreadLocal=new ThreadLocal<>();
  • 需要在业务方法中嵌入操作Context的方法,具备比较强的代码侵入性。下面代码中的getCiauserInfoFromCache就嵌入在业务代码中。
public CiaUserInfo getTokenInfo(String token) throws Exception {

        CiaUserInfo result = this.productContext.getCiaUserInfoFromCache(token);
        if(null!=result){
            return result;
        }
        else {
            result=new CiaUserInfo();
        }

        //...get user from db
        this.productContext.setCiaUserInfoToCache(result);
        return result;
    }
  • 需要自己实现缓存KEY生成器,如果是多参数,复杂对象会导致编写KEY的难度成倍提升

基于Spring Cache来实现

创建Context的目的无非就是将数据存储在ThreadLocal中充当请求级别的缓存,如果缓存是基于Spring Cache,那么上面的缺点就会不攻自破。

我找了下并没有找到基于ThreadLocal实现的缓存,大家如果有找到的可以发给我。

由于我们只关心存储(过期策略本文暂时还是延用上一篇的利用注解拦截形式去手工释放),所以实现难度并不大,因为我们完全可以参考Spring Cache的默认提供的内存级别的缓存ConcurrentMapCacheManager,整体效果如下图所示。

创建ThreadLocalCacheManager

只需要实现CacheManager这个接口即可,所有的缓存都放在一个Map中管理。

  • getCache,这个方法非常重要,用来获取缓存对象,如果容器中不存在则自动创建并更新到容器中。

这个方法中展现了并发情况下的操作,面试时这种题经常会被问到,看看Spring Cache的实现还是很有帮助的。

  • 构建函数支持无参也支持可以传入缓存名称的有参函数。有参的作用时提前初始化缓存,无参就只能等到真正调用缓存时才会创建。
  • getCacheNames,返回容器中所有缓存的名称,这点在释放整个缓存容器的缓存时非常重要。
public class ThreadLocalCacheManager implements CacheManager {

    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);

    public ThreadLocalCacheManager(){

    }

    public ThreadLocalCacheManager(String... cacheNames) {
        setCacheNames(Arrays.asList(cacheNames));
    }

    protected Cache createConcurrentMapCache(String name) {
        return new ThreadLocalCache(name);
    }

    public void setCacheNames(Collection<String> cacheNames) {
        if (cacheNames != null) {
            for (String name : cacheNames) {
                this.cacheMap.put(name, createConcurrentMapCache(name));
            }
        }
    }

    @Override
    public Cache getCache(String name) {
        Cache cache = this.cacheMap.get(name);
        if (cache == null) {
            synchronized (this.cacheMap) {
                cache = this.cacheMap.get(name);
                if (cache == null) {
                    cache = createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }

    @Override
    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(this.cacheMap.keySet());
    }
}

创建ThreadLocalCache

这个类是真正的缓存实现,继承AbstractValueAdaptingCache这个抽象类即可。相比内存缓存实现主要的区别就是存储的介质由ConcurrentMap变更为ThreadLocal,目的是每个线程单独一份缓存。

  • init,这是个协助函数,主要是将一个Map对象存入ThreadLocal中
  • lookup,根据key从缓存中取得对象,但不是很理解这个方法为什么叫lookup,看着别扭
  • getName,获取当前缓存的名称,一般在设置缓存名称时我的习惯时类名+方法名
  • getNativeCache,返回缓存的具体实现
  • put,就是更新或者插入缓存对象
  • putIfAbsent,put操作时先判断是否存在,如果存在返回缓存中的值,如果不存在就插入
  • evict,移除某个KEY
  • clear,清除缓存中的所有内容
public class ThreadLocalCache extends AbstractValueAdaptingCache {

    private final String name;

    private final ThreadLocal<ConcurrentMap> storeThreaLocal=new ThreadLocal<>();

    private void init(){
        if(null==this.storeThreaLocal.get()){
            this.storeThreaLocal.set(new ConcurrentHashMap());
        }
    }

    public ThreadLocalCache(String name){
        this(name,true);
    }

    protected ThreadLocalCache(String name,boolean allowNullValues) {
        super(allowNullValues);
        this.name=name;

        this.init();

    }

    @Override
    protected Object lookup(Object key) {
        return this.storeThreaLocal.get().get(key);
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        return this.storeThreaLocal.get();
    }

    @Override
    public void put(Object key, Object value) {
        this.storeThreaLocal.get().put(key,value);

    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        Object existing = this.storeThreaLocal.get().putIfAbsent(key, toStoreValue(value));
        return toValueWrapper(existing);
    }

    @Override
    public void evict(Object key) {
        this.storeThreaLocal.get().remove(key);
    }

    @Override
    public void clear() {
        this.storeThreaLocal.get().clear();
    }
}

配置缓存

配置非常简单,启动缓存注解,指定缓存容器。

    <cache:annotation-driven cache-manager="cacheManager"/>

    <bean id="cacheManager" class="core.cache.ThreadLocalCacheManager">

    </bean>

代码中增加Cache注解

增加@Cacheable注解,指定缓存名称以及缓存容器名称即可。相比Context方案就解决了缓存代码侵入性的问题,而且可以利用SpringCache的众多优点,比如缓存条件,缓存KEY的生成规则等等。

    @Cacheable(value = "CiaService.getCiaUserInfo",cacheManager = "cacheManager")
    @Override
    public CiaUserInfo getCiaUserInfo(String token) {
       //...
    }

释放缓存

由于有线程池的存在,所以如果不手动清除存储于ThreadLocal中的缓存数据,那么会影响我们最初的需求:请求级缓存。暂时还是通过特殊的注解来完成。通过cacheManager获取所有的缓存,然后依次执行释放操作。

 @Autowired
 private CacheManager cacheManager;
 @After("pointCut()")
    public void after(JoinPoint joinPoint) throws ProductServiceException {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();

        LocalCacheContext localCacheContext= targetMethod.getAnnotation(LocalCacheContext.class);
        if(null!=localCacheContext){
            Collection<String> cacheNames= this.cacheManager.getCacheNames();
            if(null!=cacheNames) {
                for(String cacheName :cacheNames) {
                    this.cacheManager.getCache(cacheName).clear();
                }
            }
        }
    }

如何优雅的释放缓存

上面的缓存释放是通过注解来完成的,这个注解只能加在入口函数上,是有一定限制的,如果加错了缓存就有可能在请求的中途被错误的清除。像Web容器就有非常多的方案,比如HandleInterceptor是请求级别的,可以非常方便的在请求前请求后增加一些自定义的功能。由于我这边的微服务是dubbo实现,所以可以在dubbo提供的方案中找一找,也许会有收获。

时间: 2024-10-10 07:08:25

变通实现微服务的per request以提高IO效率(二)的相关文章

变通实现微服务的per request以提高IO效率

*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* BLOCKS =============================================================================*/ p, blockquote, ul, ol, dl, table, pre { margin: 15px 0; } /* HEAD

实现微服务

变通实现微服务的per request以提高IO效率 效率 同一次业务操作过程中,往往会出现某种操作被重复执行,逻辑上来讲如果只执行一次是最理想的.这里所指的操作特指一些IO操作,比如从数据库中获取登录人的信息,也就是说如果一次请求中包含5个小逻辑,这5个小逻辑包含3次获取用户信息的操作,理想的情况是3次只有一次是从数据库中加载,其余的两次从缓存中获取. 多次调用,每个服务实现独自请求用户信息. 一次调用,多次读取.首先从Context中加载,如果失败从数据库中加载,最后将结果存入Context

微服务架构—自动化测试全链路设计

背景 被忽视的软件工程环节 - DEVTESTOPS 微服务架构下测试复杂度和效率问题 开发阶段 unitTest mock 外部依赖 连调阶段 mock 外部依赖 自动化测试阶段 mock 需求 autoTest Mock Gateway 浮出水面 轻量级版本实现 整体逻辑架构 将 mock parameter 纳入服务框架标准 request contract 使用 AOP + RestEasy HttpClientRequest SPI 初步实现 Mock 总结 背景 从 SOA 架构到现

zuul feign微服务间文件上传

feign传文件 需求 文件微服务负责管理文件,具体的文件上传下载逻辑都在此模块. openAPI负责向app用户提供头像上传的接口,实现具体业务逻辑. zuul是网关,负责路由转发.用户直接访问网关. 头像文件==>zuul==>openAPI==>文件微服务 增加引用包 <dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form</

从前世看今生,从JavaEE到微服务

我有一个习惯,接触到新概念.新技术出现后,就会探究他的前世今生.来龙去脉,正所谓"太阳底下没有新鲜事",喜欢从对比中找到价值点,不如此就觉得理解不透彻,就觉得少了点什么.微服务的概念出现后,由于又有了服务这个词,大家往往和面向服务架构做对比,类似文章即便不是汗牛充栋,也可算作车载斗量.但由于SOA 架构是企业架构层面的一种方法,视角比较宏观(例如建设银行新一代系统就是采用SOA架构),再者SOA涉及的标准规范例如XML.SOAP.WSDL.UDDI.SCA/SDO等又偏重在互联互通的协

年终盘点篇:2017年度微服务调查报告出炉

[IT168 调查报告]如果在诸多热门云计算技术中,诸如容器.微服务.DevOps.OpenStack 等,找出一个最火的方向,那么非微服务莫属.尽管话题炙手可热,但对传统行业来说,微服务落地和方法论目前处于起步阶段. 本报告于2017年11月份展开,从驱动因素.落地现状.和容器关系.架构体系.未来趋势和落地方法论等方面对微服务进行了分析.希望能够为传统企业微服务决策.规划和实施提供依据和解决办法. 一.驱动因素 传统行业对IT效率的变革需求是微服务成长土壤,业务模式创新重塑导致系统更新频繁.应

Kubernetes才是微服务和DevOps的桥梁

一.从企业上云的三大架构看容器平台的三种视角 一切都从企业上云的三大架构开始. 如图所示,企业上的三大架构为IT架构,应用架构和数据架构,在不同的公司,不同的人,不同的角色,关注的重点不同. 对于大部分的企业来讲,上云的诉求是从IT部门发起的,发起人往往是运维部门,他们关注计算,网络,存储,试图通过云计算服务来减轻CAPEX和OPEX. 有的公司有ToC的业务,因而累积了大量的用户数据,公司的运营需要通过这部分数据进行大数据分析和数字化运营,因而在这些企业里面往往还需要关注数据架构. 从事互联网

漫谈何时从单体架构迁移到微服务?

面对微服务如火如荼的发展,很多人都在了解,学习希望能在自己的项目中帮得上忙,当你对微服务的庐山真面目有所了解后,接下来就是说服自己了,到底如何评估微服务,什么时候使用微服务,什么时间点最合适,需要哪些技术储备和资源投入等等,这些都是你需要面对和解决的. 本文从单体架构,微服务架构,微服务风险评估,微服务落地条件等几个方面探讨微服务的落地过程,希望对你有所启发. 讲解微服务之前,我们先简单了解下单体架构. 一.单体架构 单体架构的优点: 快速开发和验证想法,证明产品思路是否可行 投入资源和成本,包

【CHRIS RICHARDSON 微服务系列】使用微服务重构单体应用-7

编者的话 |本文来自 Nginx 官方博客,是「Chris Richardson 微服务」系列的最后一篇.第一篇介绍了微服务架构模块,并且讨论了使用微服务的优缺点.随后的文章讨论了微服务的不同方面,包括使用 API 网关.进程间通讯.服务发现.事件驱动的数据管理,以及部署微服务.本篇将讨论从单体应用迁移到微服务的策略. 作者介绍:Chris Richardson,是世界著名的软件大师,经典技术著作<POJOS IN ACTION>一书的作者,也是 cloudfoundry.com 最初的创始人