本項目基于springboot+ spring-boot-starter-data-redis+redisson
簡單的redis demo可以參考:SpringBoot整合redis
github地址:https://github.com/weiess/redis-and-Redisson.git
redis大家工作的時候都很多,筆者根據自己經驗總結下redis的幾個數據類型,常用場景,也歡迎大家留言總結自己的經驗;
hash
hash在redis里可以存儲對象,當然string也可以,只不過hash相比較string,效率更高一點,而且功能也很強大,可以實現簡單的購物車:
下面先給大家看下簡單的存儲對象hash實現:
/*
* hash實現存儲對象
* */
@Test
public void testHsetpojo(){
User user = new User();
user.setId(123);
user.setAge(20);
user.setAddr("北京");
user.setName("yang");
Map<String,Object> map = BeanUtils.beanToMap(user);
String key = "user";
redisUtil.hmset(key,map);
System.out.println(redisUtil.hmget(key));
System.out.println("id="+redisUtil.hget(key,"id"));
String key2 = "user:"+user.getId();
redisUtil.hmset(key2,map);
System.out.println(redisUtil.hmget(key2));
}
這里的redisUtil是筆者封好的工具類,源碼在文章最底下。
hash存儲對象的時候可以把user當作key,例如代碼中的key,因為hash是 key item value三種結構,可以把后面的item和value看成一個map,這樣結構就是key map<obj,obj>,方便理解。
實際項目中可以給key加個標示符,
比如key = userId:a123456,這個大家可以根據項目來定義。
可以用hash實現購物車功能:
例如:
代碼如下:
/*
* hash實現購物車
* */
@Test
public void testcar(){
String key ="carUser:123456";
redisUtil.del(key);
Map map = new HashMap();
map.put("book:a11111",1);
map.put("book:a11112",2);
map.put("book:a11113",3);
boolean b = redisUtil.hmset(key,map);
System.out.println("key = "+redisUtil.hmget(key));
//增加book:a11111的數量
redisUtil.hincr(key,"book:a11111",1);
System.out.println(redisUtil.hmget(key));
//減少book:a11112的數量
redisUtil.hincr(key,"book:a11112",-3);
//或者redisUtil.hdecr(key,"book:a11111",1);
System.out.println(redisUtil.hmget(key));
//獲取所有key1的field的值
System.out.println("hegetall="+redisUtil.hmget(key));
//獲取key下面的map數量
System.out.println("length="+redisUtil.hlen(key));
//刪除某個key下的map
redisUtil.hdel(key,"book:a11112");
System.out.println(redisUtil.hmget(key));
}
hash里的key就是當前用戶的購物車,map就是商品(map里的key是商品id,map的v是數量)
功能實現注釋都有。注意這里的減操作是可以為負數的,所以大家一定要注意判斷負數的情況。
list
常見的list可以分為下面三個數據結構方便大家理解:
- stack(棧)= LPUSH + LPOP
- Queue(隊列)= LPUSH + RPOP
- BlockingMQ(阻塞隊列)= LPUSH + BRPOP
@Test
public void testList(){
String key = "a123456";
redisUtil.del(key);
String v1 = "aaaaa";
String v2 = "bbbbb";
String v3 = "ccccc";
List list = new ArrayList();
list.add(v1);
list.add(v2);
list.add(v3);
boolean b1 = redisUtil.lSet(key,list);
System.out.println(redisUtil.lGet(key,0,-1));
System.out.println(redisUtil.lGetIndex(key,0));
System.out.println(redisUtil.lpop(key));
System.out.println(redisUtil.rpop(key));
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.del(key);
redisUtil.rpush(key,v1);
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.rpush(key,v2);
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.lpush(key,v3);
System.out.println(redisUtil.lGet(key,0,-1));
}
實際工作中常用于消息推送,比如vx訂閱號推送,微博推送
代碼實現:
@Test
public void testVX(){
String key = "VXuser:a123456";
redisUtil.del(key);
String message1 = "a1";
String message2 = "b2";
String message3 = "c3";
//訂閱號a發表了一片文章,文章id是a1
redisUtil.lpush(key,message1);
//訂閱號b發表了一片文章,文章id是b2
redisUtil.lpush(key,message2);
//訂閱號b發表了一片文章,文章id是c3
redisUtil.lpush(key,message3);
//用戶獲取
System.out.println(redisUtil.lGet(key,0,-1));
}
比如user:a23456訂閱了這個訂閱號a,訂閱號a寫了一片內容,只需要加入user:a123456的隊列中,user就能查看到,這里的redisUtil.lGet(key,0,-1)表示獲取key下的所有v。
set
set常用于一些數學運算,他有求交集,并集,差集的功能:
@Test
public void testset(){
String key1 = "a1";
redisUtil.del(key1);
String key2 = "a2";
redisUtil.del(key2);
redisUtil.sSet(key1,1,2,3,4,5);
System.out.println("key1="+redisUtil.sGet(key1));
redisUtil.sSet(key2,1,2,5,6,7);
System.out.println("key1="+redisUtil.sGet(key2));
//獲取key的數量
System.out.println("length="+redisUtil.sGetSetSize(key1));
//取key1和key2的交集
System.out.println("交集="+redisUtil.sIntersect(key1,key2));
//取key1和key2的差集
System.out.println("差集="+redisUtil.sDifference(key1,key2));
//取key1和key2的并集
System.out.println("并集="+redisUtil.sUnion(key1,key2));
//取key1的隨機一個數
System.out.println("隨機數="+redisUtil.sRandom(key1));
System.out.println("key1="+redisUtil.sGet(key1));
//取key1的隨機一個數,并且這個值在key中刪除
System.out.println("隨機數="+redisUtil.spop(key1));
System.out.println("key1="+redisUtil.sGet(key1));
}
實際工作中可以實現抽獎,共同關注,推薦好友等功能:
@Test
public void testSet2(){
String key ="act:123456";
redisUtil.del(key);
long l = redisUtil.sSet(key,"a1","a2","a3","a4","a5");
System.out.println(redisUtil.sGet(key));
//抽獎
System.out.println(redisUtil.spop(key));
String user1 = "vxuser:a123456";
String user2 = "vxuser:b123456";
String user3 = "vxuser:c123456";
String user4 = "vxuser:d123456";
String user5 = "vxuser:e123456";
String user6 = "vxuser:f123456";
redisUtil.del("gzuser1");
redisUtil.del("gzuser2");
//gzuser1關注user2,user3,user6
redisUtil.sSet("gzuser1",user2,user3,user6);
//gzuser2關注user1,user3,user4,user5
redisUtil.sSet("gzuser2",user1,user3,user4,user5);
//共同好友
System.out.println("共同好友"+redisUtil.sIntersect("gzuser1","gzuser2"));
//你關注的好友也關注了他
Set<String> set = redisUtil.sUnion(user1,user2);
//這里取并集,放在中間表bj里
for (String s:set){
redisUtil.sSet("bj",s);
}
System.out.println(redisUtil.sGet("bj"));
System.out.println("你關注的好友也關注了他"+redisUtil.sDifference("bj","gzuser1"));
}
zset
zset相比較set一個有序,一個無序,具體功能大同小異,我就不多說。
string
簡單的string基本功能我就不多說了,這里要說的是分布式常見的setnx命令實現分布式鎖:
setnx命令其實表示『SET if Not eXists』(如果不存在,則 SET)的簡寫。設置成功,返回 1 ;設置失敗,返回 0 。
如果set 的key已經在redis里了,你再setnx,就會失敗,但是你繼續用set命令,是會覆蓋當前的key,且返回成功。
/*
* setnx 實現最簡單的分布式鎖
* */
@Test
public void setlock() {
DefaultRedisScript script = new DefaultRedisScript();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/deleteLua.lua")));
script.setResultType(Long.class);
String key ="product:001";
String value = Thread.currentThread().getId()+"";
try {
boolean result = redisUtil.setnx(key,value,100);
if(!result){
System.out.println("系統繁忙中");
} else {
System.out.println("這里是你業務代碼");
}
}catch (Exception e){
e.printStackTrace();
}finally {
Object result = redisUtil.execute(script,Collections.singletonList(key),value);
System.out.println(result);
// if (value.equals(redisUtil.get(key))){
// redisUtil.del(key);
//
// }
}
}
這是最簡單的分布式鎖實現,我給大家簡單解釋下:
首先,DefaultRedisScript這個類是為了讀取lua腳本,這里筆者的finally代碼塊用了lua腳本刪除redis的key,其實
Object result = redisUtil.execute(script,Collections.singletonList(key),value);
這行代碼等價于下面這行代碼
if (value.equals(redisUtil.get(key))){
redisUtil.del(key);
}
為什么要用lua腳本呢,更多的是為了原子性,如果用if操作,要執行兩部,在大型的環境下很容易出問題,而lua腳本就不會,他把兩個步驟合成一個步驟去執行,這樣保證原子性。
我給大家看下lua刪除key的代碼,目錄在
lua代碼:
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
注意這里的lua腳本可以放在static下,但是他的返回值也就是 script.setResultType(Long.class)必須和lua腳本的返回值一致,如果你返回的是string那就是script.setResultType(String.class),數字類型必須是Long,如果你寫Integer,會報java.lang.IllegalStateException錯誤,這個lua腳本執行成功返回1,所以大家要注意。
至于鎖的時間這個可以根據業務來定,如果你設置的超時間比較短,但是執行的業務代碼所用時間大于你設置的超時時間,你可以開一個線程,再去設置一個新的過期時間。由于分布式鎖在實際工作中可能更復雜一點,所以redisson幫我們更好的實現了分布式鎖,而且還有單機,哨兵,集群,主從等多個模式。
單機redisson
@Test
public void testsingleRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if ( res){
System.out.println("這里是你的業務代碼");
}else{
System.out.println("系統繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
這里注意下幾個Rlock 常用的幾個方法:
void lock(long leaseTime, TimeUnit unit);
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
常用的以上三個加鎖
第一表示lock表示去加鎖,加鎖成功,沒有返回值,繼續執行下面代碼;但是如果redis已經有這個鎖了,它會一直阻塞,直到鎖的時間失效,再繼續往下執行
第二個兩個參數的trylock表示嘗試去加鎖(第一個參數表示key的失效時間),加鎖成功,返回true,繼續執行true下面代碼;但是如果redis已經有這個鎖了,它會返回false,執行false的代碼塊,且不等待
第三個三個參數的trylock表示嘗試去加鎖(第一個參數表示等待時間,第二個參數表示key的失效時間),加鎖成功,返回true,繼續執行true下面代碼;如果返回false,它會等待第一個參數設置的時間,然后去執行false下面的代碼
當然redisson的功能不僅如此,它還同時還為分布式鎖提供了異步執行的相關方法
@Test
public void testsingleRedissonSync(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
lock.lockAsync();
lock.lockAsync(100,TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(3,100, TimeUnit.SECONDS);
if ( res.get()){
System.out.println("這里是你的業務代碼");
}else{
System.out.println("系統繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
上面的代碼效果其實和boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException這個方法差不多「嘗試去加鎖(第一個參數表示等待時間,第二個參數表示key的失效時間),加鎖成功,返回true,繼續執行true下面代碼;如果返回false,它會等待第一個參數設置的時間,然后去執行false下面的代碼」。
主從redisson
@Test
public void testMSRedisson(){
Config config = new Config();
config.useMasterSlaveServers()
.setMasterAddress("redis://127.0.0.1:6379")
.addSlaveAddress("redis://127.0.0.1:6380", "redis://127.0.0.1:6381");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("這里是你的業務代碼");
}else{
System.out.println("系統繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
哨兵redisson
@Test
public void testSentineRedisson(){
Config config = new Config();
config.useSentinelServers()
.addSentinelAddress("redis://127.0.0.1:26379")
.addSentinelAddress("redis://127.0.0.1:26389")
.addSentinelAddress("redis://127.0.0.1:26399");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("這里是你的業務代碼");
}else{
System.out.println("系統繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
集群redisson
@Test
public void testClusterRedisson(){
Config config = new Config();
config.useClusterServers()
// 集群狀態掃描間隔時間,單位是毫秒
.setScanInterval(2000)
//cluster方式至少6個節點(3主3從,3主做sharding,3從用來保證主宕機后可以高可用)
.addNodeAddress("redis://127.0.0.1:6379" )
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("這里是你的業務代碼");
}else{
System.out.println("系統繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
上面就是就是各種模式的redisson實現,鎖的代碼很簡單,主要是就是修改下redisson配置,其實redisson功能遠比這個更豐富,大家可以一起去學習學習