真正的缓存之王,GoogleGuava只是弟弟
前面刚说到GuavaCache,他的优点是封装了get,put操作;提供线程安全的缓存操作;提供过期策略;提供回收策略;缓存监控。当缓存的数据超过最大值时,使用LRU算法替换。这一篇我们将要谈到一个新的本地缓存框架:CaffeineCache。它也是站在巨人的肩膀上GuavaCache,借着他的思想优化了算法发展而来。
本篇博文主要介绍CaffineCache的使用方式,以及CaffineCache在SpringBoot中的使用。1。CaffineCache在算法上的优点WTinyLFU
说到优化,CaffineCache到底优化了什么呢?我们刚提到过LRU,常见的缓存淘汰算法还有FIFO,LFU:FIFO:先进先出,在这种淘汰算法中,先进入缓存的会先被淘汰,会导致命中率很低。LRU:最近最少使用算法,每次访问数据都会将其放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可。仍然有个问题,如果有个数据在1分钟访问了1000次,再后1分钟没有访问这个数据,但是有其他的数据访问,就导致了我们这个热点数据被淘汰。LFU:最近最少频率使用,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。这样就避免了LRU不能处理时间段的问题。
上面三种策略各有利弊,实现的成本也是一个比一个高,同时命中率也是一个比一个好。GuavaCache虽然有这么多的功能,但是本质上还是对LRU的封装,如果有更优良的算法,并且也能提供这么多功能,相比之下就相形见绌了。
LFU的局限性:在LFU中只要数据访问模式的概率分布随时间保持不变时,其命中率就能变得非常高。比如有部新剧出来了,我们使用LFU给他缓存下来,这部新剧在这几天大概访问了几亿次,这个访问频率也在我们的LFU中记录了几亿次。但是新剧总会过气的,比如一个月之后这个新剧的前几集其实已经过气了,但是他的访问量的确是太高了,其他的电视剧根本无法淘汰这个新剧,所以在这种模式下是有局限性。
LRU的优点和局限性:LRU可以很好的应对突发流量的情况,因为他不需要累计数据频率。但LRU通过历史数据来预测未来是局限的,它会认为最后到来的数据是最可能被再次访问的,从而给与它最高的优先级。
在现有算法的局限性下,会导致缓存数据的命中率或多或少的受损,而命中略又是缓存的重要指标。HighScalability网站刊登了一篇文章,由前Google工程师发明的WTinyLFU一种现代的缓存。CaffineCache就是基于此算法而研发。Caffeine因使用WindowTinyLfu回收策略,提供了一个近乎最佳的命中率。当数据的访问模式不随时间变化的时候,LFU的策略能够带来最佳的缓存命中率。然而LFU有两个缺点:
首先,它需要给每个记录项维护频率信息,每次访问都需要更新,这是个巨大的开销;
其次,如果数据访问模式随时间有变,LFU的频率信息无法随之变化,因此早先频繁访问的记录可能会占据缓存,而后期访问较多的记录则无法被命中。
因此,大多数的缓存设计都是基于LRU或者其变种来进行的。相比之下,LRU并不需要维护昂贵的缓存记录元信息,同时也能够反应随时间变化的数据访问模式。然而,在许多负载之下,LRU依然需要更多的空间才能做到跟LFU一致的缓存命中率。因此,一个现代的缓存,应当能够综合两者的长处。
TinyLFU维护了近期访问记录的频率信息,作为一个过滤器,当新记录来时,只有满足TinyLFU要求的记录才可以被插入缓存。如前所述,作为现代的缓存,它需要解决两个挑战:
一个是如何避免维护频率信息的高开销;
另一个是如何反应随时间变化的访问模式。
首先来看前者,TinyLFU借助了数据流Sketching技术,CountMinSketch显然是解决这个问题的有效手段,它可以用小得多的空间存放频率信息,而保证很低的FalsePositiveRate。但考虑到第二个问题,就要复杂许多了,因为我们知道,任何Sketching数据结构如果要反应时间变化都是一件困难的事情,在BloomFilter方面,我们可以有TimingBloomFilter,但对于CMSketch来说,如何做到TimingCMSketch就不那么容易了。TinyLFU采用了一种基于滑动窗口的时间衰减设计机制,借助于一种简易的reset操作:每次添加一条记录到Sketch的时候,都会给一个计数器上加1,当计数器达到一个尺寸W的时候,把所有记录的Sketch数值都除以2,该reset操作可以起到衰减的作用。
WTinyLFU主要用来解决一些稀疏的突发访问元素。在一些数目很少但突发访问量很大的场景下,TinyLFU将无法保存这类元素,因为它们无法在给定时间内积累到足够高的频率。因此WTinyLFU就是结合LFU和LRU,前者用来应对大多数场景,而LRU用来处理突发流量。
在处理频率记录的方案中,你可能会想到用hashMap去存储,每一个key对应一个频率值。那如果数据量特别大的时候,是不是这个hashMap也会特别大呢。由此可以联想到BloomFilter,对于每个key,用n个byte每个存储一个标志用来判断key是否在集合中。原理就是使用k个hash函数来将key散列成一个整数。
在WTinyLFU中使用CountMinSketch记录我们的访问频率,而这个也是布隆过滤器的一种变种。如下图所示:
c5d0788bdf7f8eeb8f40d9d7b83ffb1f。png
202111271504460601。png
如果需要记录一个值,那我们需要通过多种Hash算法对其进行处理hash,然后在对应的hash算法的记录中1,为什么需要多种hash算法呢?由于这是一个压缩算法必定会出现冲突,比如我们建立一个byte的数组,通过计算出每个数据的hash的位置。比如张三和李四,他们两有可能hash值都是相同,比如都是1那byte〔1〕这个位置就会增加相应的频率,张三访问1万次,李四访问1次那byte〔1〕这个位置就是1万零1,如果取李四的访问评率的时候就会取出是1万零1,但是李四命名只访问了1次啊,为了解决这个问题,所以用了多个hash算法可以理解为long〔〕〔〕二维数组的一个概念,比如在第一个算法张三和李四冲突了,但是在第二个,第三个中很大的概率不冲突,比如一个算法大概有1的概率冲突,那四个算法一起冲突的概率是1的四次方。通过这个模式我们取李四的访问率的时候取所有算法中,李四访问最低频率的次数。所以他的名字叫CountMinSketch。2。使用
CaffeineCache的github地址:点我。
目前的最新版本是:dependencygroupIdcom。github。benmanes。caffeinegroupIdcaffeineartifactIdversion2。6。2versiondependency2。1缓存填充策略
CaffeineCache提供了三种缓存填充策略:手动、同步加载和异步加载。1。手动加载
在每次getkey的时候指定一个同步的函数,如果key不存在就调用这个函数生成一个值。手动加载paramkeyreturnpublicObjectmanulOperator(Stringkey){CacheString,ObjectcacheCaffeine。newBuilder()。expireAfterWrite(1,TimeUnit。SECONDS)。expireAfterAccess(1,TimeUnit。SECONDS)。maximumSize(10)。build();如果一个key不存在,那么会进入指定的函数生成valueObjectvaluecache。get(key,tsetValue(key)。apply(key));cache。put(hello,value);判断是否存在如果不存返回nullObjectifPresentcache。getIfPresent(key);移除一个keycache。invalidate(key);returnvalue;}publicFunctionString,ObjectsetValue(Stringkey){returntkeyvalue;}3。异步加载
AsyncLoadingCache是继承自LoadingCache类的,异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型。
如果要以同步方式调用时,应提供CacheLoader。要以异步表示时,应该提供一个AsyncCacheLoader,并返回一个CompletableFuture。异步加载paramkeyreturnpublicObjectasyncOperator(Stringkey){AsyncLoadingCacheString,ObjectcacheCaffeine。newBuilder()。maximumSize(100)。expireAfterWrite(1,TimeUnit。MINUTES)。buildAsync(ksetAsyncValue(key)。get());returncache。get(key);}publicCompletableFutureObjectsetAsyncValue(Stringkey){returnCompletableFuture。supplyAsync((){returnkeyvalue;});}2。2回收策略
Caffeine提供了3种回收策略:基于大小回收,基于时间回收,基于引用回收。1。基于大小的过期方式
基于大小的回收策略有两种方式:一种是基于缓存大小,一种是基于权重。根据缓存的计数进行驱逐LoadingCacheString,ObjectcacheCaffeine。newBuilder()。maximumSize(10000)。build(keyfunction(key));根据缓存的权重来进行驱逐(权重只是用于确定缓存大小,不会用于决定该缓存是否被驱逐)LoadingCacheString,Objectcache1Caffeine。newBuilder()。maximumWeight(10000)。weigher(keyfunction1(key))。build(keyfunction(key));
maximumWeight与maximumSize不可以同时使用。2。基于时间的过期方式基于固定的到期策略进行退出LoadingCacheString,ObjectcacheCaffeine。newBuilder()。expireAfterAccess(5,TimeUnit。MINUTES)。build(keyfunction(key));LoadingCacheString,Objectcache1Caffeine。newBuilder()。expireAfterWrite(10,TimeUnit。MINUTES)。build(keyfunction(key));基于不同的到期策略进行退出LoadingCacheString,Objectcache2Caffeine。newBuilder()。expireAfter(newExpiryString,Object(){OverridepubliclongexpireAfterCreate(Stringkey,Objectvalue,longcurrentTime){returnTimeUnit。SECONDS。toNanos(seconds);}OverridepubliclongexpireAfterUpdate(NonnullStrings,NonnullObjecto,longl,longl1){return0;}OverridepubliclongexpireAfterRead(NonnullStrings,NonnullObjecto,longl,longl1){return0;}})。build(keyfunction(key));
Caffeine提供了三种定时驱逐策略:
expireAfterAccess(long,TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期。
expireAfterWrite(long,TimeUnit):在最后一次写入缓存后开始计时,在指定的时间后过期。
expireAfter(Expiry):自定义策略,过期时间由Expiry实现独自计算。
缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。3。基于引用的过期方式
Java中四种引用类型
引用类型被垃圾回收时间用途生存时间强引用StrongReference从来不会对象的一般状态JVM停止运行时终止软引用SoftReference在内存不足时对象缓存内存不足时终止弱引用WeakReference在垃圾回收时对象缓存gc运行后终止虚引用PhantomReference从来不会可以用虚引用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知JVM停止运行时终止当key和value都没有引用时驱逐缓存LoadingCacheString,ObjectcacheCaffeine。newBuilder()。weakKeys()。weakValues()。build(keyfunction(key));当垃圾收集器需要释放内存时驱逐LoadingCacheString,Objectcache1Caffeine。newBuilder()。softValues()。build(keyfunction(key));
注意:AsyncLoadingCache不支持弱引用和软引用。
Caffeine。weakKeys():使用弱引用存储key。如果没有其他地方对该key有强引用,那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等,因此这会导致整个缓存使用身份()相等来比较key,而不是使用equals()。
Caffeine。weakValues():使用弱引用存储value。如果没有其他地方对该value有强引用,那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等,因此这会导致整个缓存使用身份()相等来比较key,而不是使用equals()。
Caffeine。softValues():使用软引用存储value。当内存满了过后,软引用的对象以将使用最近最少使用(leastrecentlyused)的方式进行垃圾回收。由于使用软引用是需要等到内存满了才进行回收,所以我们通常建议给缓存配置一个使用内存的最大值。softValues()将使用身份相等(identity)()而不是equals()来比较值。
Caffeine。weakValues()和Caffeine。softValues()不可以一起使用。3。移除事件监听CacheString,ObjectcacheCaffeine。newBuilder()。removalListener((Stringkey,Objectvalue,RemovalCausecause)System。out。printf(Keyswasremoved(s)n,key,cause))。build();4。写入外部存储
CacheWriter方法可以将缓存中所有的数据写入到第三方。LoadingCacheString,Objectcache2Caffeine。newBuilder()。writer(newCacheWriterString,Object(){Overridepublicvoidwrite(Stringkey,Objectvalue){写入到外部存储}Overridepublicvoiddelete(Stringkey,Objectvalue,RemovalCausecause){删除外部存储}})。build(keyfunction(key));
如果你有多级缓存的情况下,这个方法还是很实用。
注意:CacheWriter不能与弱键或AsyncLoadingCache一起使用。5。统计
与GuavaCache的统计一样。CacheString,ObjectcacheCaffeine。newBuilder()。maximumSize(10000)。recordStats()。build();
通过使用Caffeine。recordStats(),可以转化成一个统计的集合。通过Cache。stats()返回一个CacheStats。CacheStats提供以下统计方法:hitRate():返回缓存命中率evictionCount():缓存回收数量averageLoadPenalty():加载新值的平均时间3。SpringBoot中默认CacheCaffineCache
SpringBoot1。x版本中的默认本地cache是GuavaCache。在2。x(SpringBoot2。0(spring5))版本中已经用CaffineCache取代了GuavaCache。毕竟有了更优的缓存淘汰策略。
下面我们来说在SpringBoot2。x版本中如何使用cache。1。引入依赖:dependencygroupIdorg。springframework。bootgroupIdspringbootstartercacheartifactIddependencydependencygroupIdcom。github。benmanes。caffeinegroupIdcaffeineartifactIdversion2。6。2versiondependency2。添加注解开启缓存支持
添加EnableCaching注解:SpringBootApplicationEnableCachingpublicclassSingleDatabaseApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(SingleDatabaseApplication。class,args);}}3。配置文件的方式注入相关参数
properties文件spring。cache。cachenamescache1spring。cache。caffeine。specinitialCapacity50,maximumSize500,expireAfterWrite10s
或Yaml文件spring:cache:type:caffeinecachenames:userCachecaffeine:spec:maximumSize1024,refreshAfterWrite60s
如果使用refreshAfterWrite配置,必须指定一个CacheLoader。不用该配置则无需这个bean,如上所述,该CacheLoader将关联被该缓存管理器管理的所有缓存,所以必须定义为CacheLoaderObject,Object,自动配置将忽略所有泛型类型。importcom。github。benmanes。caffeine。cache。CacheLoader;importorg。springframework。context。annotation。Bean;importorg。springframework。context。annotation。Configuration;author:rickiyangdate:2019615description:ConfigurationpublicclassCacheConfig{相当于在构建LoadingCache对象的时候build()方法中指定过期之后的加载策略方法必须要指定这个Bean,refreshAfterWrite60s属性才生效returnBeanpublicCacheLoaderString,ObjectcacheLoader(){CacheLoaderString,ObjectcacheLoadernewCacheLoaderString,Object(){OverridepublicObjectload(Stringkey)throwsException{returnnull;}重写这个方法将oldValue值返回回去,进而刷新缓存OverridepublicObjectreload(Stringkey,ObjectoldValue)throwsException{returnoldValue;}};returncacheLoader;}}
Caffeine常用配置说明:initialCapacity〔integer〕:初始的缓存空间大小maximumSize〔long〕:缓存的最大条数maximumWeight〔long〕:缓存的最大权重expireAfterAccess〔duration〕:最后一次写入或访问后经过固定时间过期expireAfterWrite〔duration〕:最后一次写入后经过固定时间过期refreshAfterWrite〔duration〕:创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存weakKeys:打开key的弱引用weakValues:打开value的弱引用softValues:打开value的软引用recordStats:开发统计功能注意:expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。maximumSize和maximumWeight不可以同时使用weakValues和softValues不可以同时使用
需要说明的是,使用配置文件的方式来进行缓存项配置,一般情况能满足使用需求,但是灵活性不是很高,如果我们有很多缓存项的情况下写起来会导致配置文件很长。所以一般情况下你也可以选择使用bean的方式来初始化Cache实例。
下面的演示使用bean的方式来注入:packagecom。rickiyang。learn。cache;importcom。github。benmanes。caffeine。cache。CacheLoader;importcom。github。benmanes。caffeine。cache。Caffeine;importorg。apache。commons。compress。utils。Lists;importorg。springframework。cache。CacheManager;importorg。springframework。cache。caffeine。CaffeineCache;importorg。springframework。cache。support。SimpleCacheManager;importorg。springframework。context。annotation。Bean;importorg。springframework。context。annotation。Configuration;importorg。springframework。context。annotation。Primary;importjava。util。ArrayList;importjava。util。List;importjava。util。concurrent。TimeUnit;author:rickiyangdate:2019615description:ConfigurationpublicclassCacheConfig{创建基于Caffeine的CacheManager初始化一些key存入returnBeanPrimarypublicCacheManagercaffeineCacheManager(){SimpleCacheManagercacheManagernewSimpleCacheManager();ArrayListCaffeineCachecachesLists。newArrayList();ListCacheBeanlistsetCacheBean();for(CacheBeancacheBean:list){caches。add(newCaffeineCache(cacheBean。getKey(),Caffeine。newBuilder()。recordStats()。expireAfterWrite(cacheBean。getTtl(),TimeUnit。SECONDS)。maximumSize(cacheBean。getMaximumSize())。build()));}cacheManager。setCaches(caches);returncacheManager;}初始化一些缓存的keyreturnprivateListCacheBeansetCacheBean(){ListCacheBeanlistLists。newArrayList();CacheBeanuserCachenewCacheBean();userCache。setKey(userCache);userCache。setTtl(60);userCache。setMaximumSize(10000);CacheBeandeptCachenewCacheBean();deptCache。setKey(userCache);deptCache。setTtl(60);deptCache。setMaximumSize(10000);list。add(userCache);list。add(deptCache);returnlist;}classCacheBean{privateStringkey;privatelongttl;privatelongmaximumSize;publicStringgetKey(){returnkey;}publicvoidsetKey(Stringkey){this。keykey;}publiclonggetTtl(){returnttl;}publicvoidsetTtl(longttl){this。ttlttl;}publiclonggetMaximumSize(){returnmaximumSize;}publicvoidsetMaximumSize(longmaximumSize){this。maximumSizemaximumSize;}}}
创建了一个SimpleCacheManager作为Cache的管理对象,然后初始化了两个Cache对象,分别存储user,dept类型的缓存。当然构建Cache的参数设置我写的比较简单,你在使用的时候酌情根据需要配置参数。4。使用注解来对cache增删改查
我们可以使用spring提供的Cacheable、CachePut、CacheEvict等注解来方便的使用caffeine缓存。
如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为primary,在Cacheable注解中没指定cacheManager则使用标记为primary的那个。
cache方面的注解主要有以下5个:Cacheable触发缓存入口(这里一般放在创建和获取的方法上,Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存)CacheEvict触发缓存的eviction(用于删除的方法上)CachePut更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)Caching将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)CacheConfig在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
说一下Cacheable和CachePut的区别:
Cacheable:它的注解的方法是否被执行取决于Cacheable中的条件,方法很多时候都可能不被执行。
CachePut:这个注解不会影响方法的执行,也就是说无论它配置的条件是什么,方法都会被执行,更多的时候是被用到修改上。
简要说一下Cacheable类中各个方法的使用:publicinterfaceCacheable{要使用的cache的名字AliasFor(cacheNames)String〔〕value()default{};同value(),决定要使用那个些缓存AliasFor(value)String〔〕cacheNames()default{};使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分Stringkey()default;用来生成key,与key()不可以共用StringkeyGenerator()default;设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字StringcacheManager()default;使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用StringcacheResolver()default;使用SpEL表达式设定出发缓存的条件,在方法执行前生效Stringcondition()default;使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的valueStringunless()default;用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法则只允许一个线程通过去执行方法booleansync()defaultfalse;}
基于注解的使用方法:packagecom。rickiyang。learn。cache;importcom。rickiyang。learn。entity。User;importorg。springframework。cache。annotation。CacheEvict;importorg。springframework。cache。annotation。CachePut;importorg。springframework。cache。annotation。Cacheable;importorg。springframework。stereotype。Service;author:rickiyangdate:2019615description:本地cacheServicepublicclassUserCacheService{查找先查缓存,如果查不到,会查数据库并存入缓存paramidCacheable(valueuserCache,keyid,synctrue)publicvoidgetUser(longid){查找数据库}更新保存paramuserCachePut(valueuserCache,keyuser。id)publicvoidsaveUser(Useruser){todo保存数据库}删除paramuserCacheEvict(valueuserCache,keyuser。id)publicvoiddelUser(Useruser){todo保存数据库}}
如果你不想使用注解的方式去操作缓存,也可以直接使用SimpleCacheManager获取缓存的key进而进行操作。
注意到上面的key使用了spEL表达式。SpringCache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名称位置描述示例methodNameroot对象当前被调用的方法名root。methodnamemethodroot对象当前被调用的方法root。method。nametargetroot对象当前被调用的目标对象实例root。targettargetClassroot对象当前被调用的目标对象的类root。targetClassargsroot对象当前被调用的方法的参数列表root。args〔0〕cachesroot对象当前方法调用使用的缓存列表root。caches〔0〕。nameArgumentName执行上下文当前被调用的方法的参数,如findArtisan(Artisanartisan),可以通过artsian。id获得参数artsian。idresult执行上下文方法执行后的返回值(仅当方法执行后的判断有效,如unlesscacheEvict的beforeInvocationfalse)result
注意:
1。当我们要使用root对象的属性作为key时我们也可以将root省略,因为Spring默认使用的就是root对象的属性。如Cacheable(keytargetClassmethodNamep0)
2。使用方法参数时我们可以直接使用参数名或者p参数index。如:Cacheable(valueuserCache,keyid)Cacheable(valueuserCache,keyp0)
宝妈产后抑郁怎么办?我生完宝宝已经一个多月的时间了,最近一段时间我总是心情不好,而且动不动就发脾气,晚上经常失眠,本来我以为是坐月子和婆婆吵架,心情郁闷。谁知道去医院检查是产后抑郁,虽然产后抑郁已……
太惊艳!杭州多地花开正好,短途赏花游走起春节假期还剩最后一天。近日,杭州许多地方花开正浓,短途赏花游,要不要约起?孤山梅花孤山梅花,常伴于后山沿湖地带,品种主要有江梅、绿萼、细枝朱砂等,数量约有300株。……
青海的中国之最(二)中国海拔最高的拦河大坝龙羊峡水电站大坝位于青海省境内的龙羊峡水电站是黄河上游第一座大型梯级水电站,库区海拔高程26003000米,龙羊峡大坝被誉为万里黄河第一坝。大坝坝高178米,主坝长396米,大坝全长1140米……
满身花雨又归来沧海霁月,落崖惊风。月下酒何苦,风中心何痛。江湖夜雨十年灯,明日秋风万重。在灯红酒绿的都市中,在缠绵悱恻的低回处,在两三点雨山前,七八颗星天外。人,如同帘外的乳燕,在风雨中几度……
入不敷出的口袋我的生活也是头条一缕阳光照射着大地,有麦芽的清香,有油菜花的金黄,还有麦穗的手舞足蹈。我的生活就是鸡毛蒜皮,一地的烂泥。古人云,说出的委屈,哪都不是委屈。说出的困难,也不……
最新消息!男篮大名单面临调整,赵睿伤退,郭艾伦拒绝队长职务北京时间1月26日,大年初5,值此新春佳节之际,祝福各位网友朋友们身体健康,万事顺意!让我们来继续关注中国篮球,关注CBA联赛。目前CBA联赛已经进入间歇期,各支球队都在积极调……
少女的柔情头条创作挑战赛在那一个春天的午后,我在一处地方看见了一位年轻的少女,觉得她特别得美。回到家里,便对这一切思考一下,写在白纸上。今天打开来,看到这些文字,便为此写上一段文字……
奇遇如意甘肃邂逅旷野美途提起冬天的甘肃,你会先想到什么?甘肃冰雪旅游资源富集,祁连山终年白雪皑皑,嘉峪雄关、张掖七彩丹霞等,冬日冰雪奇观琼光璀璨、美如仙境!瑞雪飘逸……
更新詹姆斯最新历史数据统计湖人VS开拓者(202323)詹姆斯出场:第1403场湖人121112开拓者今日数据:37分11板4助2帽投篮(24投14中)三分(7中2)本场里程碑:连续得分上双:1133(……
马光远2023年,捍卫市场经济!在达沃斯论坛上,国务院副总理刘鹤在公开演讲中总结了过去10年中国经济发展的五个经验:一是必须坚持发展是第一要务,坚持以经济建设为中心;二是必须坚持社会主义市场经济改……
红色引擎助推高质量发展山东海化集团推进党建与生产经营融合纪实岁末年初,从山东海化集团传出喜讯:2022年经营业绩在去年高点上起跳,亮点频现,又创新高。全年打赢疫情防控与生产经营双战役,产销两旺、质效双捷,实现利税总额23。31亿元,同比……
中国好声音十年,盘点红过冠军的10大非冠军选手必须周深领衔十年《中国好声音》,捧红了梁博、张碧晨和单依纯三位冠军。但其实在当今的华语乐坛,红起来的非冠军选手,远比当红的冠军多。今天我们来按时间顺序,盘点好声音史上最红的10大非冠……