redis
1.redis介紹
Redis定義
Redis是C語言開發的一個開源的高性能鍵值對(key-value)數據庫,可以用作數據庫,緩存,消息中間件,他是一個種非關係型數據庫。
特點
- 性能優秀,數據在內存中,讀寫速度非常快,支持10W的併發請求。
- 單進程單線程是線程安全的
- 豐富的數據類型,String,list,hash,set,zset
- 支持數據的持久化,可以將內存中的數據保存到磁盤中,重啓時加載
- 主從複製,哨兵,高可用可以用作分佈式鎖,可以作爲消息中間件使用,支持發佈訂閱。
2.redis安裝
1.直接到https://github.com/MicrosoftArchive/redis/releases下載,我這裏說一下解壓版本的安裝。
2.直接解壓redis-xx.zip
3.進入目錄,在redis.windows.conf中搜索requirepassword設置redis的認證密碼。
4.在命令行進入redis的安裝目錄執行:redis-server.exe --service-install redis.windows.conf
5.在windows 服務中找到redis啓動。
3.redis的數據類型
string(字符串)
string是redis最基本的類型,一個key對應一個value
string類型是二進制安全的,redis的string可以包含任何數據。
string類型最大能存儲512MB
使用命令 set,get 就可以實現存儲string
127.0.0.1:6379> set xpf "heixiaofei"
OK
127.0.0.1:6379> get xpf
"heixiaofei"
127.0.0.1:6379>
Hash(哈希)
Redis Hash 是一個鍵值(key=>value)對集合
Redis Hash 是一個string類型的field和value的映射表,hash適合用於存儲對象
Redis存儲hash使用,HMSET,HGET 命令,每個hash可以存儲40多億鍵值對
127.0.0.1:6379> del xpf
(integer) 1
127.0.0.1:6379> hmset xpf filed1 "xxxx" field2 "yyyy"
OK
127.0.0.1:6379> hget xpf field2
"yyyy"
127.0.0.1:6379> hget xpf filed1
"xxxx"
127.0.0.1:6379>
List<列表>
Redis列表是簡單的字符串列表,按照插入順序排序。
Redis 的列表最多可存儲40多億元素
127.0.0.1:6379> del xpf
(integer) 1
127.0.0.1:6379> lpush xpf redis
(integer) 1
127.0.0.1:6379> lpush xpf mongodb
(integer) 2
127.0.0.1:6379> lpush xpf rabbitMQ
(integer) 3
127.0.0.1:6379> lrange xpf 0 10
1) "rabbitMQ"
2) "mongodb"
3) "redis"
127.0.0.1:6379> lrange xpf 0 2
1) "rabbitMQ"
2) "mongodb"
3) "redis"
127.0.0.1:6379>
Set<集合>
Redis的Set是string類型的無序集合
集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是o(1)
sadd命令,添加一個string元素到key對應的set集合中,成功返回1,存在返回0(集合內元素的唯一性)
127.0.0.1:6379> del xpf
(integer) 1
127.0.0.1:6379> sadd xpf redis
(integer) 1
127.0.0.1:6379> sadd xpf mongodb
(integer) 1
127.0.0.1:6379> sadd xpf rabbitMQ
(integer) 1
127.0.0.1:6379> sadd xpf redis
(integer) 0
127.0.0.1:6379> smembers xpf
1) "redis"
2) "rabbitMQ"
3) "mongodb"
127.0.0.1:6379>
zset(sorted set:有序集合)
Redis zset和set一樣也是string類型元素的集合,且不允許重複的成員
每個元素都會關聯一個double類型的分數,redis通過分數爲集合中元素升序排列
zset中成員是唯一的,但分數(score)可以重複
zadd 命令
127.0.0.1:6379> del xpf
(integer) 1
127.0.0.1:6379> zadd xpf 4 redis
(integer) 1
127.0.0.1:6379> zadd xpf 3 mongodb
(integer) 1
127.0.0.1:6379> zadd xpf 2 rabbitMQ
(integer) 1
127.0.0.1:6379> zadd xpf 2 redis
(integer) 0
127.0.0.1:6379> zrangebyscore xpf 0 1000
1) "rabbitMQ"
2) "redis"
3) "mongodb"
127.0.0.1:6379> zadd xpf 6 redis
(integer) 0
127.0.0.1:6379> zrangebyscore xpf 0 1000
1) "rabbitMQ"
2) "mongodb"
3) "redis"
127.0.0.1:6379>
<!--從這裏可以看出,當第二次加同一個元素時,返回的結果爲0,但是score發生了改變。排序發生了變化 -->
4.Redis的發佈訂閱
Redis支持發佈訂閱的消息通信模式,發送者(pub)發送消息,訂閱者(sub)接收消息
當有新消息通過PUBLISH命令發送給頻道channel1時,這個消息就會被髮送給它的三個客戶端:
訂閱 redisChat 頻道
D:\developmentTools\redis\redis-3.2>redis-cli
127.0.0.1:6379> auth xykj.8387
OK
127.0.0.1:6379> subscribe redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
1) "message"
2) "redisChat"
給 RedisChat 頻道推送消息
127.0.0.1:6379> publish redisChat "redis is a great caching technique"
(integer) 1
127.0.0.1:6379>
這個時候訂閱消息的客戶端就會接收到發送的消息。
3) "redis is a great caching technique"
5.redis的事務
一個事務從開始到執行會經歷以下三個階段:
- 開始事務。
- 命令入隊。
- 執行事務。
127.0.0.1:6379> multi //開始事務
OK
127.0.0.1:6379> set a aaa //命令入隊
QUEUED
127.0.0.1:6379> set b bbb //命令入隊
QUEUED
127.0.0.1:6379> set c ccc //命令入隊
QUEUED
127.0.0.1:6379> exec //執行事務
1) OK
2) OK
3) OK
127.0.0.1:6379>
單個redis命令執行時原子性的,但redis沒有在事務上增加任何維持原子性的機制,所以redis事務的執行並不是原子性的。
批量指令並非原子化的操作,中間某條指令的失敗不會導致前面的指令回滾,也不會導致後面的指令不執行。
6.redis數據備份與恢復
數據備份
redis SAVE 命令用於創建當前數據庫的備份
127.0.0.1:6379> save
OK
該命令在redis的安裝目錄中創建dump.rdb文件
使用BGSAVE 命令也可以創建redis備份文件,在後臺運行
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379>
數據恢復
將備份文件dump.rdb移動到redis安裝目錄並啓動服務,獲取redis目錄使用 config get dir 命令
127.0.0.1:6379> config get dir
1) "dir"
2) "D:\\developmentTools\\redis\\redis-3.2"
7.SpringBoot 集成 redis
源碼下載地址:
https://github.com/prettycharacter/boot_redis.git
1.創建項目
使用idea創建一個SpringBoot項目
File→new→project→Spring Initializr→next…
2.引入依賴
<!--加入redis依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.編寫application.yml配置文件
# 增加redis的配置
spring:
redis:
#redis的數據庫索引,默認爲0
database: 0
#redis服務器地址
host: 127.0.0.1
#redis 服務器端口
port: 6379
password: xykj.8387
redis:
pool:
# 連接池最大連接數
max-active: 200
# 阻塞等待時間
max-wait: -1
#最大空閒連接
max-idle: 10
#最小空閒連接
min-idle: 10
# 連接超時時間
timeout: 1000
4.自定義一個redisTemplate
SpringBoot自動幫我們在容器中生成了一個RedisTemplate和一個StringRedisTemplate。但是,這個RedisTemplate的泛型是<Object,Object>,寫代碼不方便,需要寫好多類型轉換的代碼;我們需要一個泛型爲<String,Object>形式的RedisTemplate。
package xpf.learn.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author xpf
* @date 2020/5/26 11:45
*/
@Configuration
public class RedsiConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key採用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
//hash的key也採用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
//value序列化採用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//hash 的value序列化方式採用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
5.編寫redis工具類
package xpf.learn.redis.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author xpf
* @date // :
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//=============================common============================
/**
* 指定緩存失效時間
*
* @param key 鍵
* @param time 時間(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據key 獲取過期時間
*
* @param key 鍵 不能爲null
* @return 時間(秒) 返回0代表爲永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判斷key是否存在
*
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除緩存
*
* @param key 可以傳一個值 或多個
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通緩存獲取
*
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入並設置時間
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大於0 如果time小於等於0 將設置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大於0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大於0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小於0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大於0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 鍵 不能爲null
* @param item 項 不能爲null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 獲取hashKey對應的所有鍵值
*
* @param key 鍵
* @return 對應的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 鍵
* @param map 對應多個鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 並設置時間
*
* @param key 鍵
* @param map 對應多個鍵值
* @param time 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key 鍵
* @param item 項
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key 鍵
* @param item 項
* @param value 值
* @param time 時間(秒) 注意:如果已存在的hash表有時間,這裏將會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除hash表中的值
*
* @param key 鍵 不能爲null
* @param item 項 可以使多個 不能爲null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項的值
*
* @param key 鍵 不能爲null
* @param item 項 不能爲null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash遞增 如果不存在,就會創建一個 並把新增後的值返回
*
* @param key 鍵
* @param item 項
* @param by 要增加幾(大於0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
*
* @param key 鍵
* @param item 項
* @param by 要減少記(小於0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根據key獲取Set中的所有值
*
* @param key 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據value從一個set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將數據放入set緩存
*
* @param key 鍵
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 將set數據放入緩存
*
* @param key 鍵
* @param time 時間(秒)
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 獲取set緩存的長度
*
* @param key 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值爲value的
*
* @param key 鍵
* @param values 值 可以是多個
* @return 移除的個數
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 獲取list緩存的內容
*
* @param key 鍵
* @param start 開始
* @param end 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取list緩存的長度
*
* @param key 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通過索引 獲取list中的值
*
* @param key 鍵
* @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據索引修改list中的某條數據
*
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N個值爲value
*
* @param key 鍵
* @param count 移除多少個
* @param value 值
* @return 移除的個數
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
6.寫一個測試
package xpf.learn.redis;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import xpf.learn.redis.entity.User;
import xpf.learn.redis.util.RedisUtil;
import javax.annotation.Resource;
@SpringBootTest
class RedisApplicationTests {
@Resource
private RedisUtil redisUtil;
@Test
void testRedis() {
User user = new User();
user.setUserName("xpf");
user.setPassword("xpf6960585");
user.setType("管理員");
redisUtil.set("user", user);
}
}
測試結果:redis庫中多個一個key爲user的數據。
8.啓動項目就加載緩存
1.編寫初始化類
package xpf.learn.redis.init;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import xpf.learn.redis.service.InitService;
/**
*ApplicationRunner
* 在開發中可能會有這樣的情景。需要在容器啓動的時候執行一些內容。比如讀取配置文件,數據庫連接之類的。
* SpringBoot給我們提供了兩個接口來幫助我們實現這種需求。這兩個接口分別爲CommandLineRunner和ApplicationRunner。
* 他們的執行時機爲容器啓動完成的時候。
*
*ApplicationListener 監聽器
*當我們使用spring boot項目開發時候,碰到應用啓動後做一些初始化操作,可以使用ApplicationListener。
* 比如:netty 隨着應用啓動完成後進行初始化、初始化定時任務
* @author xpf
* @date 2020/5/26 14:31
*/
@Component
public class init implements ApplicationRunner {
@Autowired
private InitService initService;
@Async
@Override
public void run(ApplicationArguments args) throws Exception {
initService.init();
}
}
2.初始化數據服務
package xpf.learn.redis.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import xpf.learn.redis.entity.User;
import xpf.learn.redis.util.RedisUtil;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author xpf
* @date 2020/5/26 15:05
*/
@Service
public class InitService {
Logger logger = LoggerFactory.getLogger(InitService.class);
@Resource
private RedisUtil redisUtil;
@Resource
private TaskExecutor taskExecutor;
public void init() {
logger.info("進入初始化緩存的方法+++++++++++++++++++++++++");
User xpf = new User();
xpf.setUserName("xpf");
xpf.setPassword("xpf696058");
xpf.setType("小神童");
redisUtil.set("xpf", xpf);
}