缓存的设计

来源:互联网 发布:json双引号转义 编辑:程序博客网 时间:2024/05/19 02:28

    要最大限度的利用有限的资源, 缓存是不可不用的技术。而如何设计、管理缓存资源就是一个至关重要的问题,并且也是充满挑战的。现在遇到的问题就是如何利用好极其有限的TTS,TTS资源的数量是以线为单位计算的。购买了N线的TTS,就以为着同时只可能有N个用户在同时使用TTS。从成本角度来说,TTS的线数越少越好;从开发的角度,如果不使用缓存,再多的TTS线数也无法在高峰期满足用户的需求,而在空闲期又是对资源的浪费。 所以现在的问题就是如何设计好TTS缓存,以最大限度的利用有限的TTS资源。

    参照Linux内核的设计策略,我目前对TTS缓存的实现如下:设置一个全局的map,作为缓存资源的容器。map的第一个元素为关键字 key ,第二个元素为 记录实体record。其中 key 是一个包含 md5 加密串的类;record 是一个包含转换后语音文件路径 audioPath ,语音文件年龄 age ,语音文件最后访问时间 lastAccessTime属性的类。之所以考虑到使用md5加密,是因为想将不定长字符串唯一的且定长的标示出来。

    在程序运行初始,每来一个新的TTS资源请求都真实占用TTS资源进行实际转换,对TTS串 md5 加密得到key,并填写 record 相应的属性,audioPath设置为转换后语音文件的路径,age初始值根据需要任意设定,lastAccessTime设为当前时间。当一个串被请求时,我们md5加密后,将得到的加密串与map中的所有key做比较,如果相同,证明此串已经被转换过,则取出其对应record的audioPath,并将record的age加一,lastAccessTime置为当前时间。但是map的大小是有限制的,当map满的时候我们就不能在向其中插入记录了,所以我们就必须设计适当的替换策略最近最少使用(LRU)是比较常用的替换算法。我们设置一个时间间隔interval,时间间隔一到,我们就进行缓存整理。遍历map中的所有记录,检查这条记录是否最近被访问过。如何算是最近被访问过呢?这就是设置lastAccessTime的作用,如果currentTime - interval < lastAccessTime < currentTime,也就是最后一次访问时间在两次缓存整理时间间隔内,那么我们就认定这条记录在最近被访问过。对所有最近被访问的纪录,我们予以"奖励",即将其的age加一。那么我们什么时候从map中淘汰记录呢?当最近没有访问该记录时,我们将其的age减一,以示"惩罚",当age减为0时,我们就将其从map中删除。这样既考虑到了对多次访问的记录的照顾(年龄较大),又防止了老龄化的出现(最近未访问的记录要"折寿")。

    以上策略基本是按照linux内核页面管理策略来实现的,还没有与电信应用的实际相结合。如用户高峰期、用户低谷的考虑。当用户高峰期时,我们这个策略可能没有问题,但是用户低谷时,很多记录将不在最近被访问到,"折寿"会进行的很快,那么当用户再次来临的时候,就需要再次由TTS进行实际合成语音。此时替换淘汰策略就显得略为残酷了,可以考虑对不同的时间段采用不用的策略;或者将替换淘汰策略改为触发式的,即当缓存容量到达一定的比例后再进行淘汰。

    再者,考虑到对TTS的最大利用,我们的淘汰策略确实应该更加温柔一些。而当前的策略可能出现的情况是,有可能出现缓存map中无任何记录的时候,也就是所有记录都"折寿"到挂的情况。所以可以考虑设置一个缓存最低限,如当缓存占用率只有30%时,不再进行淘汰替换,将他们作为濒临灭绝的物种保护起来。这样做实际上是减轻了TTS的压力,因为当缓存容量较低的时候,是没有必要进行替换的。

    还有一个要考虑的地方就是资源共享的问题。因为map是一个全局的,对他的一切访问、修改都需要加锁lock。而加解锁也会一定程度上影响缓存的具体实现,因为频繁的加解锁,也会影响用户对TTS资源的使用。

    关于缓存的改进设计有时间另起文了。

 

原创粉丝点击