<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
快取就是資料交換的緩衝區(稱作Cache [ kæʃ ] ),是存貯資料的臨時地方,一般讀寫效能較高。
快取有很多中實現場景:對於web開發,常見的有如下幾種:
而我們的Redis快取功能就是屬於在應用層快取 。
作用:毫無疑問,就是提高讀寫的效率,有效降低後端伺服器的負載,有效降低響應時間。
成本:任何東西都有兩面性,快取在帶來高效的讀寫效率的同時,也有著對應的從成本。
比如:資料一致性成本、程式碼維護成本、運維成本等。
如下圖
原本的模型應該是使用者端傳送請求給資料庫,資料庫返回資料給使用者端,而Reids的快取模型就是在原有的基礎上,在中間加上一層Redis(經典的中介軟體思想~)使用者每次都會先去redis中查詢資料,如果未命中才會去資料庫中查詢資料,並寫入Reis當中,這麼一來,用於下次需要相同的資料的時候,就可以在Reis當中進行獲取,又因為Redis的高讀寫效率,實現了快取的效果~
基於上述的Redis快取模型,我們可以得出下面的快取新增邏輯:
程式碼實現:(直接看Service層實現)
每次查詢到商品使用者資訊後,新增快取。
package com.hmdp.service.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL; /** * <p> * 服務實現類 * </p> * */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result queryShopById(Long id) { //1.去redis中查詢商品是否存在 String key = CACHE_SHOP_KEY+id; String shopJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(shopJson)){ //2.存在,直接返回給使用者 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } //3.不存在,帶著id去資料庫查詢是否存在商品 Shop shop = getById(id); if (shop == null){ //4.不存在,返回錯誤資訊 Result.fail("商品資訊不存在!"); } //5.存在,存入redis stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop)); //6.返回商品資訊 return Result.ok(shop); } }
使用Redis做快取時,快取還是要及時更新的,不然就會出現資料庫和快取資料不一致的情況,也是我們常說的Redis成本——資料一致性成本
快取大致有以下三種更新策略:
1. 記憶體淘汰策略:這種策略沒有維護成本,這是利用Redis的記憶體淘汰機制,當記憶體不足的時候自動淘汰,下次請求時再繼續存入資料。
這種策略模型優點在於沒有維護成本,但是記憶體不足這種無法預定的情況就導致了快取中會有很多舊的資料,資料一致性差。
2. 超時剔除:這種策略就是比較實用的,就是給快取新增TTL存活時間,下次查詢是更新快取。
這種策略資料一致性一般,維護成本有但是較低,一般用於兜底方案~
3.主動更新策略:就是我們程式設計師手動的進行資料庫和快取之間的更新,但資料庫更新時,快取也進行更新。
這種策略資料一致性就是最高的(畢竟自己動手,豐衣足食),但同時維護成本也是最高的。
那麼,又該如何選擇快取更新策略呢?
我的覺得應該是根據業務場景不同來選擇不同的更新策略:
當資料一致性要求低時:l使用記憶體淘汰機制。例如店鋪型別的查詢快取。
當有高一致性需求:使用主動更新,並以超時剔除作為兜底方案。例如店鋪詳情查詢的快取
上述提到的主動更新策略,無疑是維護成本最高的,但具體又有哪些維護方式呢?怎麼去做主動更新維護呢?
如下圖:主動更新主要分為下面三種:
又因為後兩種實現方式過於複雜,所以重點說第一種。
第一種:Cache Aside Pattern,由快取呼叫者進行操作,就是在我們資料庫進行更新時,對快取也進行更新。
這又引出了好幾個問題了~
1. 快取更新?是更新快取還是直接刪除快取?
2. 如何保證資料庫更新和快取更新同時成功或失敗?
3. 操作時應該先操作快取還是先運算元據庫?
第一個問題:
我想說,快取更新如果是資料更新的話,每次更新資料庫都要對快取資料進行更新,有太多無效的讀寫操作,所以操作快取時,選擇刪除快取~
第二個問題:
要做到兩個操作一致性,第一想到的就應該是事務。
解決方案:當我們是單體系統時,將快取和資料庫操作放在同一個事務裡。
當我們是分散式系統時,利用TTC等分散式事務方案
最後一個問題:先運算元據庫還是先操作快取?
就只有下面兩種情況:
1.先刪除快取,再運算元據庫
2.先運算元據庫,再刪除快取
我們可以兩種情況都來看看~
第一種情況,先刪除快取的情況,我們想的正常的不會出現問題的操作流程(左邊)和操作會出現問題的流程(右邊)
補充一下:這邊兩個執行緒最初的資料庫和快取資料都假定為10~
出現問題的情況為我們執行緒1先刪除快取,執行緒2未命中快取,直接查到資料庫中資料為10並寫入快取中,而執行緒1線上程2後寫入快取後,把資料庫更新成了20,這樣最後資料庫資料為20,而快取中的資料為10,這就導致資料不一致了。
2.先運算元據庫的情況
正確情況(左邊)錯誤情況(右邊),資料庫和快取最初資料也都還是10~
第二種先運算元據庫的方式,它出現的錯誤情況主要是,執行緒1執行緒查詢了快取,未命中後去查詢資料庫的同時,執行緒2更新了資料庫,並且刪除了快取,然後執行緒1才把資料庫中查出來的10寫入快取,導致快取為10,資料庫為20。
終上所述,第一種失敗的情況是比第二種失敗的情況多的,因為第一種情況出現錯誤的時間是在刪除快取並更新資料庫後,執行緒2有著充足的時間在這段時間內寫入快取,而對於第二種情況來說,出現問題發生在查完資料庫到寫入快取這段時間內,這段時間幾乎是毫秒級別的,執行緒2在這段時間內更新資料庫並刪除快取,顯然機率是很低的(除了高高高並行的狀況下),所以我們選擇先運算元據庫在操作快取~
總結:
快取更新策略的最佳實踐方案:
1.低一致性需求:使用Redis自帶的記憶體淘汰機制
2.高一致性需求:主動更新,並以超時剔除作為兜底方案
主動更新時
進行讀操作:
進行寫操作:
修改商品快取
1. 根據id查詢店鋪時,如果快取未命中,則查詢資料庫,將資料庫結果寫入快取,並設定超時時間
2. 根據id修改店鋪時,先修改資料庫,再刪除快取
實現程式碼如下(Service層實現):
package com.hmdp.service.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL; /** * <p> * 服務實現類 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result queryShopById(Long id) { //1.去redis中查詢商品是否存在 String key = CACHE_SHOP_KEY+id; String shopJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(shopJson)){ //2.存在,直接返回給使用者 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } //3.不存在,帶著id去資料庫查詢是否存在商品 Shop shop = getById(id); if (shop == null){ //4.不存在,返回錯誤資訊 Result.fail("商品資訊不存在!"); } //5.存在,存入redis(並設定超時時間) stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); //6.返回商品資訊 return Result.ok(shop); } @Override @Transactional public Result updateShop(Shop shop) { //1.先更新資料庫 updateById(shop); //2.刪除redis快取 String key = CACHE_SHOP_KEY+shop.getId(); stringRedisTemplate.delete(key); return null; } }
這樣就能達到基本的主動快取更新啦~
快取穿透是指使用者端請求的資料在快取中和資料庫中都不存在,這樣快取永遠不會生效,這些請求都會打到資料庫。
通俗的說,就是請求的資料在資料庫和快取中都沒有,最後請求都到了資料庫。這個時候,如果有一個惡意的程式設計師,通過某種方式不斷請求一個不存在的資料,這樣就會給資料庫帶來巨大的壓力。(就怕使用者懂程式碼)
常見的快取穿透的解決方案有兩種:
1.就是給redis快取一個空物件並設定TTL存活時間
這種方式優點在於實現簡單,維護方便,但也帶來了額外的記憶體消耗和可能的短期的資料不一致。
小知識:這裡的資料不一致發生在使用者剛從redis中拿到null值恰好資料插入了這個請求需要的值而導致的資料庫Redis資料不一致。
2. 就是利用布隆過濾
通俗的說,就是中介軟體~
這種方式 優點在於記憶體消耗較小,沒有多餘的key,缺點就在於實現複雜,而且布隆過濾器有誤判的可能...
程式碼實現:這裡實現第一種
就拿上述的寫入商品資訊為例:
我們只需要在資料庫獲取資料時,如果取到空值,不直接返回404,而是將空值也存入redis中,並且在判斷快取是否命中時,判斷命中的值是不是我們傳入的空值。如果是,直接結束,不是就返回商鋪資訊
package com.hmdp.service.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.*; /** * <p> * 服務實現類 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result queryShopById(Long id) { //1.去redis中查詢商品是否存在 String key = CACHE_SHOP_KEY+id; String shopJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(shopJson)){ //2.存在,直接返回給使用者 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } //判斷命中的是否為空字串 /** * (這裡重點講一下為什麼是不等於:因為我們獲取到的shopJson為null是,上面的isNotBlank方法會返回false,導致成為了不命中的效果) */ if (shopJson != null){ return Result.fail("商品資訊不存在!"); } //3.不存在,帶著id去資料庫查詢是否存在商品 Shop shop = getById(id); if (shop == null){ // 4.將空值存入redis stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES); //5.不存在,返回錯誤資訊 Result.fail("商品資訊不存在!"); } //6.存在,存入redis(並設定超時時間) stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); //7.返回商品資訊 return Result.ok(shop); } }
總結:
快取穿透產生的原因是什麼?
使用者請求的資料在快取中和資料庫中都不存在,不斷髮起這樣的請求,給資料庫帶來巨大壓力。
快取穿透的解決方案有哪些?(3-6點為擴充套件~)
1.快取null值
2.布隆過濾
3.增強id的複雜度,避免被猜測id規律
4.做好資料的基礎格式校驗
5.加強使用者許可權校驗
6.做好熱點引數的限流
快取雪崩是指在同一時段大量的快取key同時失效或者Redis服務宕機,導致大量請求到達資料庫,帶來巨大壓力。
就是說,一群設定了有效期的key同時消失了,或者說redis罷工了,導致所有的或者說大量的請求會給資料庫帶來巨大壓力叫做快取雪崩~
解決方式也比較的簡單~
1. 給不同的key新增隨機的TTL存活時間(這種就是最簡單的,設定存貨時間隨機各不相同)
2. 利用Redis叢集(這種針對與Redis出現宕機的情況)
3. 給快取業務新增降級限流策略(SpringCloud知識點)
4. 給業務新增多級快取
快取擊穿問題也叫熱點Key問題,就是一個被高並行存取並且快取重建業務較複雜的key突然失效了,無數的請求存取會在瞬間給資料庫帶來巨大的衝擊。
大概的奔潰流程是這樣子的:
第一個執行緒,查詢redis發現未命中,然後去資料庫查詢並重建快取,這個時候因為在快取重建業務較為複雜的情況下,重建時間較久,又因為高並行的環境下,線上程1重建快取的時間內,會有其他的大量的其他執行緒進來,發現查詢快取仍未命中,導致繼續重建,如此死迴圈。
常見的解決方案有兩種:
互斥鎖的解決方案如下:
就是當執行緒查詢快取未命中時,嘗試去獲取互斥鎖,然後在重建快取資料,在這段時間裡,其他執行緒也會去嘗試獲取互斥鎖,如果失敗就休眠一段時間,並繼續,不斷重試,等到資料重建成功,其他執行緒就可以命中資料了。這樣就不會導致快取擊穿。這個方案資料一致性是絕對的,但是相對來說會犧牲效能。
實現方法:
1.獲取互斥鎖和釋放鎖的實現:
這裡我們獲取互斥鎖可以使用redis中string型別中的setnx方法 ,因為setnx方法是在key不存在的情況下才可以建立成功的,所以我們重建快取時,使用setnx來將鎖的資料加入到redis中,並且通過判斷這個鎖的key是否存在,如果存在就是獲取鎖成功,失敗就是獲取失敗,這樣剛好可以實現互斥鎖的效果。
而釋放鎖就更簡單了,直接刪除我們存入的鎖的key來釋放鎖。
//獲取鎖 public Boolean tryLock(String key){ Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } //釋放鎖方法 public void unlock(String key){ stringRedisTemplate.delete(key); }
2.實現程式碼:
具體操作流程如下圖:
實現程式碼如下:
package com.hmdp.service.impl; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisData; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.*; /** * <p> * 服務實現類 * </p> */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result queryShopById(Long id) { //互斥鎖快取擊穿 Shop shop = queryWithMutex(id); if (shop == null){ Result.fail("商品資訊不存在!"); } //8.返回商品資訊 return Result.ok(shop); } // 快取穿透解決——互斥鎖 public Shop queryWithMutex(Long id){ //1.去redis中查詢商品是否存在 String key = CACHE_SHOP_KEY+id; String shopJson = stringRedisTemplate.opsForValue().get(key); if (StrUtil.isNotBlank(shopJson)){ //2.存在,直接返回給使用者 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return shop; } //判斷命中的是否為空字串 if (shopJson != null){ return null; } Shop shop = null; String lockKey = LOCK_SHOP_KEY+id; try { //3.快取重建 //3.1嘗試獲取鎖 Boolean isLock = tryLock(lockKey); //3.2判斷獲取鎖是否成功,如果休眠重試 if (!isLock){ //休眠 Thread.sleep(50); //重試用遞迴 queryWithMutex(id); } //3.3如果成功,去資料庫查詢資料 shop = getById(id); if (shop == null){ // 4.將空值存入redis stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES); //5.不存在,返回錯誤資訊 Result.fail("商品資訊不存在!"); } //6.存在,存入redis(並設定超時時間) stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { //7.釋放鎖 unlock(lockKey); } //8.返回商品資訊 return shop; } //獲取鎖 public Boolean tryLock(String key){ Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } //釋放鎖方法 public void unlock(String key){ stringRedisTemplate.delete(key); } }
邏輯過期的處理方法主要為:
給redis快取欄位中新增一個過期時間,然後當執行緒查詢資料庫的時候,先判斷是否已經過期,如果過期,就獲取獲取互斥鎖,並開啟一個子執行緒進行快取重建任務,直到子執行緒完成任務後,釋放鎖。在這段時間內,其他執行緒獲取互斥鎖失敗後,並不是繼續等待重試,而是直接返回舊資料。這個方法雖然效能較好,但也犧牲了資料一致性。
實現方法:
1.獲取互斥鎖和釋放鎖如上
2.給redis資料新增一個過期時間(建立一個RedisData類,並封裝資料)
RedisData類:
package com.hmdp.utils; import lombok.Data; import java.time.LocalDateTime; @Data public class RedisData { private LocalDateTime expireTime; private Object data; }
//給商品資訊新增一個過期時間欄位,並存入redis當中 public void saveShop2Redis(Long id,Long expireSeconds){ Shop shop = getById(id); RedisData redisData = new RedisData(); redisData.setData(shop); redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds)); stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData)); }
然後就是業務實現了:
具體程式碼如下:
package com.hmdp.service.impl; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisData; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import static com.hmdp.utils.RedisConstants.*; /** * <p> * 服務實現類 * </p> */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource private StringRedisTemplate stringRedisTemplate; @Override public Result queryShopById(Long id) { //邏輯過期解決快取擊穿問題 // Shop shop = queryWithLogicalExpire(id); if (shop == null){ Result.fail("商品資訊不存在!"); } //8.返回商品資訊 return Result.ok(shop); } private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10); public Shop queryWithLogicalExpire(Long id){ //1.去redis中查詢商品是否存在 String key = CACHE_SHOP_KEY+id; String shopJson = stringRedisTemplate.opsForValue().get(key); //2.判斷是否命中 //3.未命中 if (StrUtil.isBlank(shopJson)){ return null; } //4.命中,先把redis中的資料反序列化成java物件 RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class); //4.1獲取過期時間 LocalDateTime expireTime = redisData.getExpireTime(); //4.2獲取商品物件 JSONObject data = (JSONObject) redisData.getData(); Shop shop = JSONUtil.toBean(data, Shop.class); //5.判斷是否過期 if (expireTime.isAfter(LocalDateTime.now())){ //未過期,直接返回shop return shop; } //6.過期,重建快取 //6.1嘗試獲取鎖,並判斷 String lockKey = LOCK_SHOP_KEY + id; Boolean isLock = tryLock(lockKey); if (isLock){ //5.2 如果成功,開啟一個獨立的執行緒,重建快取 CACHE_REBUILD_EXECUTOR.submit(()->{ try { //重建快取 this.saveShop2Redis(id,20L); } catch (Exception e) { throw new RuntimeException(e); } finally { //釋放鎖 unlock(lockKey); } }); } //6.2返回舊的商品資訊 return shop; } //獲取鎖 public Boolean tryLock(String key){ Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } //釋放鎖方法 public void unlock(String key){ stringRedisTemplate.delete(key); } //給商品資訊新增一個過期時間欄位,並存入redis當中 public void saveShop2Redis(Long id,Long expireSeconds){ Shop shop = getById(id); RedisData redisData = new RedisData(); redisData.setData(shop); redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds)); stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData)); } }
快取知識結束。
到此這篇關於Redis快取範例超詳細講解的文章就介紹到這了,更多相關Redis快取策略內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45