Redis 學習筆記 3:黑馬點評

Redis 學習筆記 3:黑馬點評

準備工作

需要先導入項目相關資源:

  • 數據庫文件 hmdp.sql
  • 后端代碼 hm-dianping.zip
  • 包括前端代碼的 Nginx

啟動后端代碼和 Nginx。

短信登錄

發送驗證碼

@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 發送短信驗證碼并保存驗證碼return userService.sendCode(phone, session);
}
@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手機號!");}String code = RandomUtil.randomNumbers(6);session.setAttribute("code", code);// 發送短信log.debug("發送短信驗證碼:{}", code);return Result.ok();}
}

登錄

@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session) {// 實現登錄功能return userService.login(loginForm, session);
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 驗證手機號和驗證碼if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手機號不合法!");}String code = (String) session.getAttribute("code");if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("驗證碼不正確!");}// 檢查用戶是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 將用戶信息保存到 sessionsession.setAttribute("user", user);return Result.ok();
}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;
}

統一身份校驗

定義攔截器:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 從 session 獲取用戶信息HttpSession session = request.getSession();User user = (User) session.getAttribute("user");if (user == null) {response.setStatus(401);return false;}// 將用戶信息保存到 ThreadLocalUserDTO userDTO = new UserDTO();userDTO.setIcon(user.getIcon());userDTO.setId(user.getId());userDTO.setNickName(user.getNickName());UserHolder.saveUser(userDTO);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

添加攔截器:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

使用 Redis 存儲驗證碼和用戶信息

用 Session 存儲驗證碼和用戶信息的系統,無法進行橫向擴展,因為多臺 Tomcat 無法共享 Session。如果改用 Redis 存儲就可以解決這個問題。

修改后的 UserService:

@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手機號!");}String code = RandomUtil.randomNumbers(6);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL);// 發送短信log.debug("發送短信驗證碼:{}", code);return Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 驗證手機號和驗證碼if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手機號不合法!");}String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + loginForm.getPhone());if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("驗證碼不正確!");}// 檢查用戶是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 將用戶信息保存到 sessionString token = UUID.randomUUID().toString(true);UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user, userDTO);try {stringRedisTemplate.opsForValue().set(LOGIN_USER_KEY + token,OBJECT_MAPPER.writeValueAsString(userDTO), LOGIN_USER_TTL);} catch (JsonProcessingException e) {e.printStackTrace();throw new RuntimeException(e);}return Result.ok(token);}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;}
}

修改后的登錄校驗攔截器:

public class LoginInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 從頭信息獲取 tokenString token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {// 缺少 tokenresponse.setStatus(401);return false;}// 從 Redis 獲取用戶信息String jsonUser = this.stringRedisTemplate.opsForValue().get(LOGIN_USER_KEY + token);UserDTO userDTO = OBJECT_MAPPER.readValue(jsonUser, UserDTO.class);if (userDTO == null) {response.setStatus(401);return false;}// 將用戶信息保存到 ThreadLocalUserHolder.saveUser(userDTO);// 刷新 token 有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

還需要添加一個更新用戶信息有效期的攔截器:

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果請求頭中有 token,且 redis 中有 token 相關的用戶信息,刷新其有效期String token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {return true;}if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(LOGIN_USER_KEY + token))) {stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);}return true;}
}

添加這個新的攔截器,并且確保其位于登錄驗證攔截器之前:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

商戶查詢

緩存

對商戶類型查詢使用 Redis 緩存以提高查詢效率:

@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryTypeList() {String jsonTypeList = stringRedisTemplate.opsForValue().get(CACHE_TYPE_LIST_KEY);if (!StringUtils.isEmpty(jsonTypeList)) {List<ShopType> typeList = JSONUtil.toList(jsonTypeList, ShopType.class);return Result.ok(typeList);}List<ShopType> typeList = this.query().orderByAsc("sort").list();if (!typeList.isEmpty()){stringRedisTemplate.opsForValue().set(CACHE_TYPE_LIST_KEY, JSONUtil.toJsonStr(typeList), CACHE_TYPE_LIST_TTL);}return Result.ok(typeList);}
}

對商戶詳情使用緩存:

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 先從 Redis 中查詢String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中沒有,從數據庫查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);}return Result.ok(shop);}
}

緩存更新策略

在編輯商戶信息時,將對應的緩存刪除:

@Override
public Result update(Shop shop) {if (shop.getId() == null) {return Result.fail("商戶id不能為空");}// 更新商戶信息this.updateById(shop);// 刪除緩存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();
}

緩存穿透

緩存穿透指如果請求的數據在緩存和數據庫中都不存在,就不會生成緩存數據,每次請求都不會使用緩存,會對數據庫造成壓力。

可以通過緩存空對象的方式解決緩存穿透問題。

在查詢商鋪信息時緩存空對象:

@Override
public Result queryById(Long id) {// 先從 Redis 中查詢String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中沒有,從數據庫查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 緩存空對象到緩存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店鋪不存在");}
}

在這里,緩存中的空對象用空字符串代替,并且將緩存存活時間設置為一個較短的值(比如說2分鐘)。

在從緩存中查詢到空對象時,返回商鋪不存在:

@Override
public Result queryById(Long id) {// 先從 Redis 中查詢String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果從緩存中查詢到空對象,表示商鋪不存在if ("".equals(jsonShop)) {return Result.fail("商鋪不存在");}// ...
}

緩存擊穿

緩存擊穿問題也叫熱點Key問題,就是一個被高并發訪問并且緩存重建業務較復雜的key突然失效了,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊。

常見的解決方案有兩種:

  • 互斥鎖
  • 邏輯過期

可以利用 Redis 做互斥鎖來解決緩存擊穿問題:

@Override
public Result queryById(Long id) {//        return queryWithCachePenetration(id);return queryWithCacheBreakdown(id);
}/*** 用 Redis 創建互斥鎖** @param name 鎖名稱* @return 成功/失敗*/
private boolean lock(String name) {Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(name, "1", Duration.ofSeconds(10));return BooleanUtil.isTrue(result);
}/*** 刪除 Redis 互斥鎖** @param name 鎖名稱*/
private void unlock(String name) {stringRedisTemplate.delete(name);
}/*** 查詢店鋪信息-緩存擊穿** @param id* @return*/
private Result queryWithCacheBreakdown(Long id) {// 先查詢是否存在緩存String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果從緩存中查詢到空對象,表示商鋪不存在if ("".equals(jsonShop)) {return Result.fail("商鋪不存在");}// 緩存不存在,嘗試獲取鎖,并創建緩存final String lockName = "lock:shop:" + id;try {if (!lock(lockName)){// 獲取互斥鎖失敗,休眠一段時間后重試Thread.sleep(50);return queryWithCacheBreakdown(id);}// 獲取互斥鎖成功,創建緩存// 模擬長時間才能創建緩存Thread.sleep(100);Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 緩存空對象到緩存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店鋪不存在");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 釋放鎖unlock(lockName);}
}

下面是用邏輯過期解決緩存擊穿問題的方式。

首先需要將熱點數據的緩存提前寫入 Redis(緩存預熱):

public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {/*** 創建店鋪緩存** @param id       店鋪id* @param duration 緩存有效時長*/public void saveShopCache(Long id, Duration duration) {Shop shop = getById(id);RedisCache<Shop> redisCache = new RedisCache<>();redisCache.setExpire(LocalDateTime.now().plus(duration));redisCache.setData(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisCache));}// ...
}
@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate ShopServiceImpl shopService;@Testpublic void testSaveShopCache(){shopService.saveShopCache(1L, Duration.ofSeconds(1));}}
@Data
public class RedisCache<T> {private LocalDateTime expire; //邏輯過期時間private T data; // 數據
}

Redis 中的緩存信息包含兩部分:過期時間和具體信息。大致如下:

{"data": {"area": "大關","openHours": "10:00-22:00","sold": 4215,// ...},"expire": 1708258021725
}

且其 TTL 是-1,也就是永不過期。

具體的緩存讀取和重建邏輯:

/*** 用邏輯過期解決緩存擊穿問題** @return*/
private Result queryWithLogicalExpiration(Long id) {//檢查緩存是否存在String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (StringUtils.isEmpty(jsonShop)) {// 緩存不存在return Result.fail("店鋪不存在");}// 緩存存在,檢查是否過期RedisCache<Shop> redisCache = JSONUtil.toBean(jsonShop, new TypeReference<RedisCache<Shop>>() {}, true);if (redisCache.getExpire().isBefore(LocalDateTime.now())) {// 如果過期,嘗試獲取互斥鎖final String LOCK_NAME = LOCK_SHOP_KEY + id;if (lock(LOCK_NAME)) {// 獲取互斥鎖后,單獨啟動線程更新緩存CACHE_UPDATE_ES.execute(() -> {try {// 模擬緩存重建的延遲Thread.sleep(200);saveShopCache(id, Duration.ofSeconds(1));} catch (Exception e) {throw new RuntimeException(e);} finally {unlock(LOCK_NAME);}});}}// 無論是否過期,返回緩存對象中的信息return Result.ok(redisCache.getData());
}

封裝 Redis 緩存工具類

可以對對 Redis 緩存相關邏輯進行封裝,可以避免在業務代碼中重復編寫相關邏輯。封裝后分別對應以下方法:

  • 設置緩存數據(TTL)
  • 設置緩存數據(邏輯過期時間)
  • 從緩存獲取數據(用空對象解決緩存穿透問題)
  • 從緩存獲取數據(用互斥鎖解決緩存擊穿問題)
  • 從緩存獲取數據(用邏輯過期解決緩存擊穿問題)

工具類的完整代碼可以參考這里。

本文的完整示例代碼可以從這里獲取。

參考資料

  • 黑馬程序員Redis入門到實戰教程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/697219.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/697219.shtml
英文地址,請注明出處:http://en.pswp.cn/news/697219.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

超市售貨|超市售貨管理小程序|基于微信小程序的超市售貨管理系統設計與實現(源碼+數據庫+文檔)

超市售貨管理小程序目錄 目錄 基于微信小程序的超市售貨管理系統設計與實現 一、前言 二、系統功能設計 三、系統實現 1、微信小程序前臺 2、管理員后臺 &#xff08;1&#xff09;商品管理 &#xff08;2&#xff09;出入庫管理 &#xff08;3&#xff09;公告管理 …

CrossOver2024虛擬機軟件的優缺點分別是什么?

CrossOver虛擬機軟件的優缺點分別如下&#xff1a; 優點&#xff1a; 無需雙系統&#xff1a;用戶可以在Mac或Linux系統上直接運行Windows應用程序&#xff0c;無需安裝雙系統&#xff0c;從而節省了硬盤空間并避免了系統切換的麻煩。易于安裝和使用&#xff1a;CrossOver具有…

文件上傳---->生僻字解析漏洞

現在的現實生活中&#xff0c;存在文件上傳的點&#xff0c;基本上都是白名單判斷&#xff08;很少黑名單了&#xff09; 對于白名單&#xff0c;我們有截斷&#xff0c;圖片馬&#xff0c;二次渲染&#xff0c;服務器解析漏洞這些&#xff0c;于是今天我就來補充一種在upload…

RAW 編程接口 TCP 簡介

一、LWIP 中 中 RAW API 編程接口中與 TCP 相關的函數 二、LWIP TCP RAW API 函數 三、LwIP_Periodic_Handle函數 LwIP_Periodic_Handle 函數是一個必須被無限循環調用的 LwIP支持函數&#xff0c;一般在 main函數的無限循環中調用&#xff0c;主要功能是為 LwIP各個模塊提供…

web前端安全性——JSONP劫持

1、JSONP概念 JSONP(JSON with Padding)是JSON的一種“使用模式”&#xff0c;可用于解決主流瀏覽器的跨域數據訪問的問題。由于同源策略&#xff0c;協議IP端口有任意不同都會導致請求跨域&#xff0c;而HTML的script元素是一個例外。利用script元素的這個開放策略&#xff0…

vscode【報錯】yarn : 無法將“yarn”項識別為 cmdlet

問題 CMD下載完yarn可以查看到yarn版本&#xff0c;但是進入到vscode控制臺報錯無法識別&#xff0c;報錯內容如下&#xff1a; vscode【報錯】yarn : 無法將“yarn”項識別為 cmdlet、函數、腳本文件或可運行程序的名稱。請檢查名稱的拼寫&#xff0c;如果包括路徑&#xff…

@ 代碼隨想錄算法訓練營第8周(C語言)|Day57(動態規劃)

代碼隨想錄算法訓練營第8周&#xff08;C語言&#xff09;|Day57&#xff08;動態規劃&#xff09; Day53、動態規劃&#xff08;● 1143.最長公共子序列 ● 1035.不相交的線 ● 53. 最大子序和 動態規劃 &#xff09; 1143.最長公共子序列 題目描述 給定兩個字符串 text1 …

C#面:i++ 和 ++i 的區別

i 先參與左邊的運算&#xff0c;之后 i 自增&#xff1b; int i 5; int result i; // result的值為5&#xff0c;i的值變為6 i i 先自增&#xff0c;之后的值&#xff0c;參與左邊的運算&#xff1b; int i 5; int result i; // result的值為6&#xff0c;i的值也為6…

【一步步由簡入深,搞定FFT,持續更新中...】

作為一個傾向于形象思維的工程師&#xff0c;總想把復雜深奧的知識搞的方便理解&#xff0c;雖然上學時學過數字信號處理&#xff0c;仍然一知半解&#xff0c;現在想借著項目中涉及到的頻譜相關知識總結下來&#xff0c;在了解中逐步完善。 好了&#xff0c;首先要明確的概念是…

ffmpeg for android編譯全過程與遇到的問題

編譯前準備 編譯環境&#xff1a;Ubuntu16&#xff0c;可自行下載VMWare最新版并百度永久許可證或在服務器上安裝Ubuntu ffmpeg源碼&#xff1a;ffmpeg4.2.2 NDK下載&#xff1a;Android NDK r21e 有條件的最好還是在Liunx平臺下編譯吧&#xff0c;Windows平臺下編譯坑更多…

【計算機網絡】數據鏈路層|封裝成幀|透明傳輸|差錯檢測|PPP協議|CSMA/CD協議

目錄 一、思維導圖 ? 二、數據鏈路層功能概述 1.數據鏈路層概述 2.數據鏈路層功能概述——封裝成幀 3.數據鏈路層功能概述——透明傳輸 4.數據鏈路層功能概述——差錯檢測 三、數據鏈路層重要協議 1.數據鏈路層重要協議&#xff1a;PPP協議 2.數據鏈路層重要協議&#x…

js設計模式:備忘錄模式

作用: 封裝的對象可以在對象觸發行為時進行狀態的記錄與保存 也可以進行狀態的回退,恢復之前的狀態 示例: class Editor{constructor(){this.allText }edit(text){this.allText text}saveNow(){return new EditorText(this.allText)}backspacing(editorText){this.allText…

護眼臺燈哪個品牌更好用?五大好用護眼臺燈大爆料!

護眼臺燈相信大家都有所耳聞或者使用過,家里有小孩的可能了解更深,畢竟是孩子學習時需要使用的小家電。現在市面上的護眼臺燈種類可以說是多到眼花繚亂,甚至有些劣質的產品摻雜在里面,或許有些寶媽已經踩過一些坑了&#xff0c;護眼臺燈究竟哪個品牌更好用&#xff1f; &#x…

這個春節,爽了

四次醫院 請了一周假&#xff0c;準備開始愉快的長假。 結果第一天小孩就發燒了&#xff0c;趕緊送醫院拿藥。回到家才發現&#xff0c;給醫生看的驗血報告是上一次的&#xff0c;那是好幾個月之前的。 但是藥已經吃了&#xff0c;這是吃錯藥了呀&#xff01;&#xff01; …

手機中有哪些逆向進化的功能

手機中有哪些逆向進化的功能&#xff1f;逆向進化是指明明很優秀的很方便的功能&#xff0c;卻因為成本或者其他工業原因莫名其妙地給取消了。 逆向進化1&#xff1a;可拆卸電池-變為不可拆卸電池。 智能手機為了追求輕薄等原因&#xff0c;所以移除了可拆卸電池功能。將電池…

GoLand 相關

goland 下載依賴 go mod tidy&#xff1a;保持依賴整潔 go mod tidy 命令的作用是清理未使用的依賴&#xff0c;并更新 go.mod 以及 go.sum 文件。 go mod tidy 和 go mod vendor 兩個命令是維護項目依賴不可或缺的工具。go mod tidy 確保了項目的 go.mod 文件精簡且準確&…

ubuntu20.04安裝實時內核補丁PREEMPT_RT

參考&#xff1a; Ubuntu 18.04安裝 RT-PREEMPT 實時內核及補丁【過程記錄】_ubuntu18.04 preempt rt linux 5.6.19-CSDN博客 https://github.com/UniversalRobots/Universal_Robots_ROS_Driver/blob/master/ur_robot_driver/doc/real_time.md當前內核&#xff1a;5.15.0-94-ge…

1.deeplabv3+網絡結構及原理

這里的網絡結構及原理可以看這篇博客&#xff0c;DeepLabV3: 在DeepLabV3基礎上引入了Decoder_matlab deeplabv3resnet101-CSDN博客該博客翻譯原論文解釋得很清楚。 一、引言 語義分割的目標是為圖像中的每個像素分配語義標簽。在這項研究中&#xff0c;考慮了兩種類型的神經網…

Vue計算屬性computed()

1. 計算屬性定義 獲取計算屬性值 <div>{{ 計算屬性名稱}}</div>創建計算屬性 let 定義的屬性ref/reactive....let 計算屬性名稱 computed(() > {//這里寫函數式,函數式里面包含定義屬性//只有這個包含的定義屬性被修改時才出發此函數式//通過計算屬性名稱co…

docker:Haoop集群

系列文章目錄 docker&#xff1a;環境安裝 docker:Web遷移 docker:Haoop集群 文章目錄 系列文章目錄前言一、宿主機選擇二、環境準備1.前置技術2.網絡環境1. docker網卡2. 分配IP 三、容器互聯三、Jdk和Hadoop安裝四、分發腳本五、啟動Hadoop總結 前言 年前學習了docker的相關…