redis中数据类型的使用,并发问题,list重复插入问题,redis使用实例-简单消息队列和排名统计
redis 5種數據類型的正確使用
redis支持5中數據類型,即string,list,hash,set,sortedset。但是什么時候應該用哪種數據類型呢?以string和list的為例來說明
其實并不是簡單的java中的list對應redis中的list,java中的string對應redis的string
list可以存到string中,取出來后可以強轉為list
redis中list數據的本質,是你需要通過redis來直接維護list的數據,如操作某個下標對應的值或者直接對該list插入和刪除數據,所以如果你需要的僅僅是對整個list的操作,不需要對list中的數據直接做操作,那么就不應該使用list類型,而應該是字符串類型。
下面的代碼用于說明:string中保存list的實例,ListOperations中leftPush和leftPushAll對list處理的區別
@RestController public class RedisController {@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping("/")public String index() {// 字符串操作類ValueOperations valueOperations = redisTemplate.opsForValue();valueOperations.set("d","dvalue");// 結果 key:d,value:dvalueSystem.out.println("key:d,value:"+valueOperations.get("d"));ArrayList<String> strings1 = new ArrayList<>();strings1.add("a");strings1.add("b");// 將list作為string直接存入和讀取valueOperations.set("e",strings1);if(valueOperations.get("e") instanceof List){List<String> e = (List<String>) valueOperations.get("e");// 結果:e.size2// 說明list存入字符串類型沒問題System.out.println("e.size"+e.size());}// list操作類ListOperations listOperations = redisTemplate.opsForList();ArrayList<String> strings = new ArrayList<>();strings.add("a");strings.add("b");strings.add("c");// 此時strings被當做一個整體,即strings作為list中一個下標對應的valuelistOperations.leftPush("list",strings);// 此時strings被當做一個list,即strings中每一個元素作為list中一個下標對應的valuelistOperations.leftPushAll("list1",strings);// leftPush方法,list.size=1// leftPushAll方法,list1.size=3System.out.println("leftPush方法,list.size="+listOperations.size("list"));System.out.println("leftPushAll方法,list1.size="+listOperations.size("list1"));redisTemplate.delete("list");redisTemplate.delete("list1");return "Greetings from Spring Boot!";} }問題:什么時候適合使用list?是代碼中數據集合是list的時候嗎?
其實并不是java代碼中使用的是list,對應到redis中的數據類型就是list;而是你java代碼對一組數據的操作是符合list的操作邏輯的時候,即需要直接操作該list中具體的數據,可以對該list中的數據不斷的寫入和讀出,你可以直接使用redis對該list數據移除和插入,注意不是操作整個list。如果是整個操作list應該是直接作為字符串類型存入,如上面的實例。
redis的lis并發插入導致的重復問題
需要記錄付費用戶的id,正常邏輯先整體刪除并整體存入
高并發時,刪除后可能存在重復存入,即list的內容被追加了,導致存在重復的數據
解決方案一:
redisTemplate.delete返回boolean,只有key存在并且確實刪除了該key對應的內容時,才會返回true表示成功刪除,所以可以先判斷delete是否成功再進行存入,如果刪除失敗則不再插入,可以避免重復插入
解決方案二:list轉化為json,采用字符串模式,key相同可以直接替換,不會存在重復的問題
因為雖然我存的數據是list,但是實際上是個對redies來時是字符串
解決方案三:
list的存儲類型直接修改為字符串類型,存入的時候直接存入list;代碼實例如下
? ? ? ? ValueOperations valueOperations = redisTemplate.opsForValue();
? ? ? ? valueOperations.setIfPresent("d","dvalue");
? ? ? ? ArrayList<String> strings1 = new ArrayList<>();
? ? ? ? strings1.add("a");
? ? ? ? strings1.add("b");
? ? ? ? valueOperations.set("e",strings1);
? ? ? ? List<String> e = (List<String>) valueOperations.get("e");
解決方案四:采用java的鎖,對delete()和push操作加synchronized鎖,兩個操作作為原子性的操作
方案五:redis事務解決,在同一個session中采用redisOperations.multi()和redisOperations.exec()
原本事務是這樣寫的
? ? ? ? ? ? ? ? ? ?redisOperations.multi();
? ? ? ? ? ? ? ? ? ? ArrayList<String> strings = new ArrayList<>();
? ? ? ? ? ? ? ? ? ? strings.add("abc");
? ? ? ? ? ? ? ? ? ? redisTemplate.delete("strings");
? ? ? ? ? ? ? ? ? ? redisOperations.exec();
但是執行的時候,redis事務報錯No ongoing transaction. Did you forget to call multi?
原因:發現上面代碼中mutil()方法和exec()方法都會從新建立新的連接,導致數據丟失
解決方法:采用RedisTemplate的SesionCallback實現在同一個Connection中
解決方案借鑒
https://www.cnblogs.com/dujiudizhimo/p/9051596.html
方案六
delete后,采用setnx方法,如果對應的key已經存在則什么都不做并返回0,否則設置key的值
推薦方案三和方案一,沒有任何阻塞,效率最高。上面涉及到的代碼在這里給出
@RestController public class RedisTransaction {@Autowiredprivate RedisTemplate redisTemplate;@RequestMapping("/trac")public String index() {// 每次調用該接口,啟動多線程模擬并發場景for(int i=0;i<5;i++){new Thread(new Task(redisTemplate)).start();}return "Greetings from Spring Boot!";} } public class Task implements Runnable {public static final String lock = "lock";private RedisTemplate redisTemplate;public Task(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;}@Overridepublic void run() {for(int i=0;i<10000;i++){ListOperations listOperations = redisTemplate.opsForList();// 有問題代碼,會有線程安全問題 即strings對應的值可能會被重復插入ArrayList<String> strings = new ArrayList<>();strings.add("abc");Boolean delete = redisTemplate.delete("strings");listOperations.leftPushAll("strings",strings);// 正確的方案五 采用redis的事務/* SessionCallback sessionCallback = new SessionCallback() {@Overridepublic Object execute(RedisOperations redisOperations) throws DataAccessException {redisOperations.multi();ArrayList<String> strings = new ArrayList<>();strings.add("abc");redisTemplate.delete("strings");listOperations.leftPushAll("strings",strings);return redisOperations.exec();}};redisTemplate.execute(sessionCallback);*/// 錯誤的方案五 采用redis的事務,報錯:No ongoing transaction. Did you forget to call multi?// 錯誤原因是原因:發現上面代碼中mutil()方法和exec()方法都會從新建立新的連接,導致數據丟失//解決方法:采用RedisTemplate的SesionCallback實現在同一個Connection中/* redisOperations.multi();ArrayList<String> strings = new ArrayList<>();strings.add("abc");redisTemplate.delete("strings");redisOperations.exec();*/// 方案一 delete成功再插入/*ArrayList<String> strings = new ArrayList<>();strings.add("abc");Boolean delete = redisTemplate.delete("strings");if(delete){listOperations.leftPushAll("strings",strings);}*///方案四 java的事務實現/*synchronized(lock){ArrayList<String> strings = new ArrayList<>();strings.add("abc");Boolean delete = redisTemplate.delete("strings");listOperations.leftPushAll("strings",strings);}*/// 只要出現了大于1的情況,就說明重復插入了if(listOperations.size("strings") >1){System.out.println("con error"+listOperations.size("strings"));}}} }redis的并發競爭問題如何解決?
Redis為單進程單線程模式,采用隊列模式將并發訪問變為串行訪問。Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,但是在Jedis客戶端對Redis進行并發訪問時會發生連接超時、數據轉換錯誤、阻塞、客戶端關閉連接等問題,這些問題均是由于客戶端連接混亂造成。對此有2種解決方法:
1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內部鎖synchronized。
2.服務器角度,利用setnx實現鎖。
注:對于第一種,需要應用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問題。
redis使用實例-簡單消息隊列和排名統計
@RestController public class RedisController {@Autowiredprivate RedisTemplate redisTemplate;/*** desc: redis使用場景-簡單的消息隊列* @author mazhen* @date 2019/4/10 下午5:51*/@RequestMapping("/produce")public void produce(String message) {ListOperations listOperations = redisTemplate.opsForList();listOperations.leftPush("mq",message);}@RequestMapping("/consume")public String consume() {ListOperations listOperations = redisTemplate.opsForList();Object mq = listOperations.rightPop("mq");return mq+"";}/*** desc: redis使用場景-簡單排名統計* @author mazhen* @date 2019/4/10 下午5:51*/@RequestMapping("/score")public String score() {redisTemplate.delete("score");ZSetOperations zSetOperations = redisTemplate.opsForZSet();zSetOperations.add("score","b",99);zSetOperations.add("score","a",100);zSetOperations.add("score","c",98);zSetOperations.add("score","d",97);// 倒序取2條數據,即分數最高的是第一名,這里只要第一和第二名int topNum = 2;Set<Object> score1 = redisTemplate.opsForZSet().reverseRange("score", 0, topNum - 1);String names = "前兩名:";for(Object name1:score1){names += name1+",";}// 結果 前兩名:a,b,System.out.println(names);return names;} }?
總結
以上是生活随笔為你收集整理的redis中数据类型的使用,并发问题,list重复插入问题,redis使用实例-简单消息队列和排名统计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 0-2岁的app开发人员必读,Andro
- 下一篇: react 学习