概述:本系列博文所涉及的相關內容來源於debug親自錄製的實戰課程:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰),感興趣的小夥伴能夠點擊自行前往學習(畢竟以視頻的形式來掌握技術 會更快!) ,文章所屬專欄:緩存中間件Redis技術入門與實戰html
摘要:毫無疑問,集合Set一樣也是緩存中間件Redis中其中一個重要的數據結構,其內部存儲的元素/成員具備「惟一」、「隨機」等特性,在實際的項目開發中一樣具備至關普遍的應用場景。本文咱們將介紹並實戰一種比較典型的業務場景~「重複提交」,即如何利用集合Set的相關特性實現「用戶註冊時過濾重複提交的消息」!
前端
內容:在前面幾篇文章中,咱們介紹了Redis的數據結構~列表List,簡單介紹了其基本特性及其在實際項目中比較常見的、典型的應用場景!從本文開始,咱們將着手介紹並實戰Redis的另一種數據結構~集合Set,介紹其基本的特性、在Dos環境下的命令行列表以及在Spring Boot2.0搭建的項目下實際應用場景的代碼實戰等!java
Redis的數據結構-集合Set 跟 咱們數學中的集合Set、JavaSE中的集合Set能夠說幾乎是相同的東西,,其特性均爲: 「無序」、「惟一」,即集合Set中存儲的元素是沒有順序且不重複的!git
除此以外,其底層設計亦具備「殊途同歸」之妙,即採用哈希表來實現的,故而其相應的操做如添加、刪除、查找的複雜度都是 O(1) 。web
1、DOS命令行的實操(基於redis-cli.exe工具便可實踐)redis
下面咱們先採用 DOS下命令行的方式 來簡單的認識並實踐集合Set的相關命令,包括其常見的操做命令和「數學層面」集合的操做命令,以下圖所示:數據庫
(1)常見的操做命令無非就是「新增」、「查詢-獲取集合中的元素列表」、「查詢-獲取集合中的成員數目」、「查詢-獲取集合中隨機個數的元素列表」、「查詢-判斷某個元素是否爲集合中的成員」、「刪除-移除集合中的元素」等。api
下面咱們貼出幾個比較典型、常見的操做命令所對應的實際操做吧,其中相應命令的含義各位小夥伴能夠對照着上面那張圖進行查看!緩存
127.0.0.1:6379> SADD classOneStudents jacky xiaoming debug michael white (integer) 5 127.0.0.1:6379> SMEMBERS classOneStudents 1) "jacky" 2) "michael" 3) "debug" 4) "xiaoming" 5) "white" 127.0.0.1:6379> SCARD classOneStudents (integer) 5 127.0.0.1:6379> SADD classTwoStudents jacky xiaohong mary (integer) 3 127.0.0.1:6379> SISMEMBER jacky classOneStudents (integer) 0 127.0.0.1:6379> SISMEMBER classOneStudents jacky (integer) 1 127.0.0.1:6379> SPOP classOneStudents "white" 127.0.0.1:6379> SMEMBERS classOneStudents 1) "debug" 2) "jacky" 3) "xiaoming" 4) "michael" 127.0.0.1:6379> SRANDMEMBER classOneStudents 1 1) "jacky" 127.0.0.1:6379> SRANDMEMBER classOneStudents 3 1) "michael" 2) "xiaoming" 3) "debug" 127.0.0.1:6379> SRANDMEMBER classOneStudents 10 1) "jacky" 2) "michael" 3) "xiaoming" 4) "debug"
(2)而「數學層面」集合的操做命令則比較有意思,在這裏咱們主要介紹「交集」、「差集」和「並集」這三個操做命令,以下圖所示:安全
一樣的道理,咱們依舊貼出這幾個操做命令所對應的DOS操做,相應命令的含義各位小夥伴能夠對照着上面那張圖進行查看!
127.0.0.1:6379> SDIFF classOneStudents classTwoStudents 1) "white" 2) "xiaoming" 3) "debug" 4) "michael" 127.0.0.1:6379> SDIFF classTwoStudents classOneStudents 1) "xiaohong" 2) "mary" 127.0.0.1:6379> SINTER classOneStudents classTwoStudents 1) "jacky" 127.0.0.1:6379> SUNION classOneStudents classTwoStudents 1) "debug" 2) "jacky" 3) "xiaohong" 4) "xiaoming" 5) "michael" 6) "mary"
2、集合Set命令對應的代碼操做
基於這些操做命令,下面咱們基於Spring Boot2.0搭建的項目,以「Java單元測試」的方式先進行一波「代碼實戰」,將「Dos下的命令行操做」轉化爲實際的代碼操做,以下所示:
@Test public void method3() { log.info("----開始集合Set測試"); final String key1 = "SpringBootRedis:Set:10010"; final String key2 = "SpringBootRedis:Set:10011"; redisTemplate.delete(key1); redisTemplate.delete(key2); SetOperations<String, String> setOperations = redisTemplate.opsForSet(); setOperations.add(key1, new String[]{"a", "b", "c"}); setOperations.add(key2, new String[]{"b", "e", "f"}); log.info("---集合key1的元素:{}", setOperations.members(key1)); log.info("---集合key2的元素:{}", setOperations.members(key2)); log.info("---集合key1隨機取1個元素:{}", setOperations.randomMember(key1)); log.info("---集合key1隨機取n個元素:{}", setOperations.randomMembers(key1, 2L)); log.info("---集合key1元素個數:{}", setOperations.size(key1)); log.info("---集合key2元素個數:{}", setOperations.size(key2)); log.info("---元素a是否爲集合key1的元素:{}", setOperations.isMember(key1, "a")); log.info("---元素f是否爲集合key1的元素:{}", setOperations.isMember(key1, "f")); log.info("---集合key1和集合key2的差集元素:{}", setOperations.difference(key1, key2)); log.info("---集合key1和集合key2的交集元素:{}", setOperations.intersect(key1, key2)); log.info("---集合key1和集合key2的並集元素:{}", setOperations.union(key1, key2)); log.info("---從集合key1中彈出一個隨機的元素:{}", setOperations.pop(key1)); log.info("---集合key1的元素:{}", setOperations.members(key1)); log.info("---將c從集合key1的元素列表中移除:{}", setOperations.remove(key1, "c")); }
點擊該單元測試方法左邊的「運行」按鈕圖標,便可將該單元測試方式運行起來,其運行後的結果以下圖所示:
相應的api就不一一介紹了,其方法名能夠說是見名知意,大夥兒也能夠照着擼一擼,敲一敲,實踐事後就會發現其實也沒那麼複雜!
3、典型應用場景實戰之~用戶註冊時過濾重複提交的信息
下面咱們以實際項目開發中典型的應用場景爲案例,以實際的代碼踐行集合Set各類重要的特性,即主要有「惟一性」、「無序性」。
咱們首先以「集合Set中的元素具備惟一性」進行開刀,以「用戶註冊時過濾重複提交的信息」爲案例進行代碼實戰。
說實在的,「重複提交」的業務場景在實際的項目開發中其實並很多見,好比用戶在前端提交信息時重複點擊按鈕屢次,若是此時不採起相應的限制措施,那麼頗有可能會在數據庫表中出現多條相同的數據條目!下面咱們以「用戶註冊時重複提交信息」爲案例進行代碼實戰。
(1)工欲善其事,必先利其器,咱們首先先在數據庫創建「用戶信息表user」,其DDL以下所示:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '姓名', `email` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '郵箱', PRIMARY KEY (`id`), UNIQUE KEY `idx_email` (`email`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶表';
而後利用mybatis的代碼生成器或者逆向工程生成該數據庫表user的Entity實體信息、Mapper操做接口列表以及用於操做動態Sql的Mapper.xml,在這裏我就不貼出來其對應源碼了,各位小夥伴能夠前往文末提供的地址進行下載查看!
(2)接下來,咱們創建一個Controller,並在其中開發相應的請求方法,用於處理前端用戶提交過來的「註冊信息」,其源碼以下所示:
/** * 數據類型爲Set - 數據元素不重複(過濾掉重複的元素;判斷一個元素是否存在於一個大集合中) * @Author:debug (SteadyJack) – wx:debug0868 **/ @RestController @RequestMapping("set") public class SetController extends AbstractController { @Autowired private SetService setService; //TODO:提交用戶註冊 @RequestMapping(value = "put",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public BaseResponse put(@RequestBody @Validated User user, BindingResult result){ String checkRes=ValidatorUtil.checkResult(result); if (StrUtil.isNotBlank(checkRes)){ return new BaseResponse(StatusCode.Fail.getCode(),checkRes); } BaseResponse response=new BaseResponse(StatusCode.Success); try { log.info("----用戶註冊信息:{}",user); response.setData(setService.registerUser(user)); }catch (Exception e){ response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage()); } return response; } }
(3)其Service的處理邏輯以下所示:
/** * 集合set服務處理邏輯 * @Author:debug (SteadyJack) * @Link: weixin-> debug0868 qq-> 1948831260 **/ @Service public class SetService { private static final Logger log= LoggerFactory.getLogger(SetService.class); @Autowired private UserMapper userMapper; @Autowired private RedisTemplate redisTemplate; //TODO:用戶註冊 @Transactional(rollbackFor = Exception.class) public Integer registerUser(User user) throws Exception{ if (this.exist(user.getEmail())){ throw new RuntimeException(StatusCode.UserEmailHasExist.getMsg()); } int res=userMapper.insertSelective(user); if (res>0){ SetOperations<String,String> setOperations=redisTemplate.opsForSet(); setOperations.add(Constant.RedisSetKey,user.getEmail()); } return user.getId(); } //TODO:判斷郵箱是否已存在於緩存中 private Boolean exist(final String email) throws Exception{ //TODO:寫法二 SetOperations<String,String> setOperations=redisTemplate.opsForSet(); Long size=setOperations.size(Constant.RedisSetKey); if (size>0 && setOperations.isMember(Constant.RedisSetKey,email)){ return true; }else{ User user=userMapper.selectByEmail(email); if (user!=null){ setOperations.add(Constant.RedisSetKey,user.getEmail()); return true; }else{ return false; } } }
從該代碼中咱們能夠看出,在插入用戶信息進入數據庫以前,咱們須要判斷該用戶是否存在於緩存集合Set中,若是已經存在,則告知前端該「用戶郵箱」已經存在(在這裏咱們認爲用戶的郵箱是惟一的,固然啦,你能夠調整爲「用戶名」惟一…),若是緩存集合Set中不存在該郵箱,則插入數據庫中,並在「插入數據庫表成功」 以後,將該用戶郵箱塞到緩存集合Set中去便可。
值得一提的是,咱們在「判斷緩存Set中是否已經存在該郵箱」的邏輯中,是先判斷緩存中是否存在,若是不存在,爲了保險,咱們會再去數據庫查詢郵箱是否真的不存在,若是真的是不存在,則將其「第一次」添加進緩存Set中(這樣子能夠在某種程度避免前端在重複點擊提交按鈕時,產生瞬時高併發的現象,從而下降併發安全的風險)!
固然啦,這種寫法仍是會存在必定的問題的:即若是在插入數據庫時「掉鏈子」了,即發生異常了致使沒有插進去,可是這個時候咱們在「判斷緩存集合Set中是否存在該郵箱時已經將該郵箱添加進緩存中一次了」,故而該郵箱將永遠不能註冊了(可是實際上該郵箱並無真正插入到數據庫中哦!)
(4)既然出現了問題,那麼就得先辦法去解決,以下代碼所示,爲咱們改造後的用戶註冊的服務邏輯:
@Transactional(rollbackFor = Exception.class) public Integer registerUser(User user) throws Exception{ if (this.exist(user.getEmail())){ throw new RuntimeException(StatusCode.UserEmailHasExist.getMsg()); } int res=0; try{ res=userMapper.insertSelective(user); if (res>0){ redisTemplate.opsForSet().add(Constant.RedisSetKey,user.getEmail()); } }catch (Exception e){ throw e; }finally { //TODO:若是res不大於0,即表明插入到數據庫發生了異常, //TODO:這個時候得將緩存Set中該郵箱移除掉 //TODO:由於在判斷是否存在時 加入了一次,不移除掉的話,就永遠註冊不了該郵箱了 if (res<=0){ redisTemplate.opsForSet().remove(Constant.RedisSetKey,user.getEmail()); } } return user.getId(); }
從該服務處理邏輯中,咱們能夠得知主要使用集合Set的API方法包括:「插入」、「判斷是否爲集合中的元素」、「集合中元素的個數」、「移除集合中指定的元素」等等
最後,咱們打開Postman對該接口進行一番測試,以下幾張圖所示便可看到其最終的測試效果:
好了,本篇文章咱們就介紹到這裏了,建議各位小夥伴必定要照着文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,不然就成了「空談者」!對Redis相關技術棧以及實際應用場景實戰感興趣的小夥伴能夠我們51cto學院 debug親自錄製的課程進行學習:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰)
補充:
一、本文涉及到的相關的源代碼能夠到此地址,check出來進行查看學習:https://gitee.com/steadyjack/SpringBootRedis
二、目前debug已將本文所涉及的內容整理錄製成視頻教程,感興趣的小夥伴能夠前往觀看學習:https://edu.51cto.com/course/20384.html