一,redis是什么?
首先數據庫分為關系型數據庫和非關系型數據庫,關系型數據庫是采用關系模型來組織數據的數據庫,簡單來說就是二維表格模型,同時保證事務的一致性。
相反非關系型數據庫采用key-value形式進行存儲,是一種數據結構化存儲方法的集合,具有分布式性質。
Redis是當前比較熱門的NOSQL系統之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(區別於MySQL的二維表格的形式存儲。)遵守BSD協議、支持網絡、可基於內存亦可持久化的日志型、Key-Value數據庫,並提供多種語言的API。它通常被稱為數據結構服務器,因為值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型。
二,redis的優勢
1,性能快:redis讀取的速度是110000次/s,寫的速度是81000次/s。
2,豐富的數據類型:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)等。
3,原子性:Redis的所有操作都是原子性的,且多個客戶端同時訪問redis客戶端可獲得更新后的值。
4,持久化:集群(主從復制,分布式)。
三,redis與Memcached區別(經典面試題)
1 、redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲類型。memcache支持簡單的數據類型,String,同時還可以緩存圖片,視頻。
2 、Redis支持數據的備份,即master-slave模式的數據備份(主從復制)。
3 、Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用。
4、 redis的速度比memcached快很多
5、Memcached是多線程,非阻塞IO復用的網絡模型;Redis使用單線程的IO復用模型。
6,數據安全性:memcache掛掉后,數據便消失;redis可以定期保存到磁盤(持久化)。
四,redis常用命令及springboot操作redis
4.1,引入依賴包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.2,常用命令與代碼整合操作。
注意:redis默認使用JDK序列化方式
更多詳細命令,請參考Redis中文網:https://www.redis.net.cn/
4.3,字符串操作類型。
/**
* String - 字符串類型的操作方式
* redisTemplate.opsForValue()
*/
@Test
public void stringType(){
// 改為String序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
// redis命令:set key value
redisTemplate.opsForValue().set("age", "19");
// redis命令:get key
String age = (String) redisTemplate.opsForValue().get("age");
System.out.println("-->" + age);
// redis命令:mset key value key value ...
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
redisTemplate.opsForValue().multiSet(map);
// redis命令:mget key key key...
List<String> keys = new ArrayList<>();
keys.add("key1");
keys.add("key2");
keys.add("key3");
List values = redisTemplate.opsForValue().multiGet(keys);
System.out.println("mget -->" + values);
// redis命令:del key
Boolean boo = redisTemplate.delete("key1");
// redis命令:strlen key - 可能會因為序列化的原因造成長度不准
Long resultLong = redisTemplate.opsForValue().size("age");
System.out.println("strlen --> " + resultLong);
// redis命令:getset key value
String oldValue = (String) redisTemplate.opsForValue().getAndSet("age", "25");
System.out.println("getset --> " + oldValue);
// redis命令:getrange key start end - 可能會因為序列化的原因造成長度不准
String age1 = redisTemplate.opsForValue().get("age", 0, 1);
System.out.println("getrange --> " + age1);
// redis命令:append - 可能會因為序列化的原因造成長度不准
Integer age2 = redisTemplate.opsForValue().append("age", "26");
System.out.println("append --> " + age2);
// redis命令:incr key - 自增 - 可能會因為序列化的原因造成長度不准
Long age3 = redisTemplate.opsForValue().increment("age", 10);
System.out.println("incr -->" + age3);
// redis命令:decr key - 自減
redisTemplate.opsForValue().increment("age", -10);
Long decr = redisTemplate.getConnectionFactory().getConnection().decr("age".getBytes());
System.out.println("decr --> " + decr);
}
4.4,Hash操作類型。
/**
* Hash數據類型的操作
*/
@Test
public void hashType(){
// redis命令:mset key field value
redisTemplate.opsForHash().put("person", "name", "張三");
redisTemplate.opsForHash().put("person", "age", 19);
// redis命令:mget key field
String value = (String) redisTemplate.opsForHash().get("person", "name");
System.out.println("mget-->" + value);
// redis命令:hmset key field1 value1 field2 value2 ...
Map<String, String> map = new HashMap<>();
map.put("bookname", "Java精通之路");
map.put("price", "100.99");
redisTemplate.opsForHash().putAll("book", map);
// redis命令:hmget key field1 field2 ...
List<String> list = new ArrayList<>();
list.add("bookname");
list.add("price");
List books = redisTemplate.opsForHash().multiGet("book", list);
System.out.println("hmget-->" + books);
// redis命令:del key
redisTemplate.delete("book");
// redis命令:hdel key field1 field2...
redisTemplate.opsForHash().delete("book", "bookname", "price");
// redis命令:hexists key field
Boolean bool = redisTemplate.opsForHash().hasKey("book", "bookname");
System.out.println("hexists-->" + bool);
// redis命令:hlen key
Long length = redisTemplate.opsForHash().size("book");
System.out.println("hlen-->" + length);
// redis命令:hkeys key - 展示key對應的所有字段名稱
Set set = redisTemplate.opsForHash().keys("book");
System.out.println("hkeys-->" + set);
// redis命令:hvals key - 展示key對應的所有字段的值
List values = redisTemplate.opsForHash().values("book");
System.out.println("hvals-->" + values);
// redis命令:hgetall key - field and value
Map bookmap = redisTemplate.opsForHash().entries("book");
System.out.println("hgetall-->" + bookmap);
}
4.5,鏈表數據結構。
/**
* 鏈表數據結構
*/
@Test
public void linkedType(){
// redis命令:lpush key value1 value2...
redisTemplate.opsForList().leftPush("book", "c++");
redisTemplate.opsForList().leftPushAll("book", "c", "java");
// redis命令:rpush key value1 value2
redisTemplate.opsForList().rightPush("book", "mysql");
redisTemplate.opsForList().rightPushAll("book", "oracle", "sqlserver");
// redis命令:lindex key index
String book0 = (String) redisTemplate.opsForList().index("book", 0);
System.out.println("lindex-->" + book0);
// redis命令:llen key
Long bookLen = redisTemplate.opsForList().size("book");
System.out.println("llen-->" + bookLen);
// redis命令:lpop key
String leftBook = (String) redisTemplate.opsForList().leftPop("book");
System.out.println("lpop-->" + leftBook);
// redis命令:rpop key
String rightBook = (String) redisTemplate.opsForList().rightPop("book");
System.out.println("rpop-->" + rightBook);
// redis命令:linsert key before|after oldnode newnode
redisTemplate.opsForList().leftPush("book", "java", "pythod");
redisTemplate.opsForList().rightPush("book", "java", "jquery");
// redis命令:lrange key start end
List rangeList = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
System.out.println("lrange-->" + rangeList);
// redis命令:lset key index value
redisTemplate.opsForList().set("book", 0, "db");
// redis命令:ltrim key start end
redisTemplate.opsForList().trim("book", 1, 3);
// redis命令:lrange key start end
List rangeList2 = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
System.out.println("lrange-->" + rangeList2);
}
4.6,集合操作類型。
/**
* 集合操作
*/
@Test
public void setType(){
// redis命令:sadd
redisTemplate.opsForSet().add("person", "小明","小紅","小剛");
// redis命令:scard
Long person = redisTemplate.opsForSet().size("person");
System.out.println("scard-->" + person);
// redis命令:smembers
Set set = redisTemplate.opsForSet().members("person");
System.out.println("smembers-->" + set);
}
4.7,有序集合操作類型
/**
* 有序集合
*/
@Test
public void zsetType(){
redisTemplate.opsForZSet().add("book", "mysql", 1.5);
redisTemplate.opsForZSet().add("book", "java", 8.5);
redisTemplate.opsForZSet().add("book", "html", 10.5);
Set set = redisTemplate.opsForZSet().range("book", 0, redisTemplate.opsForZSet().size("book") - 1);
System.out.println(set);
}
五,redis高級應用
5.1,事務
與其他NoSQL不同,Redis是存在事務的,盡管沒有數據庫那么強大,但是還是非常有用,尤其是在高並發的情況中,使用redis的事務可以保證數據一致性的同時,大幅度提高數據讀寫的響應速度。
redis的事務是使用multi-exec的命令組合,使用它可以提供兩個重要保證:
1、事務是一個被隔離的操作,事務中的方法都會被redis進行序列化並按順序執行,事務在執行的過程中不會被其他客戶端的發出的命令所打斷。
2、事務是一個原子性操作,它要么全部執行、要么全部不執行。
事務的常用命令:
multi:開啟事務,之后的命令就會進入隊列,而不是馬上執行。
watch key1 [key2]...:監聽某些鍵,當被監聽的鍵在提交事務前被修改,則事務會回滾 (基於樂觀鎖機制)。
unwatch key1 [key2]...:取消監聽。
exec:執行事務,如果被監聽的鍵沒有被修改,則采用提交命令,否則就執行回滾命令。
discard:回滾事務。
事務的開啟及提交如下圖:
從上圖中看出,當開始事務時進行操作,命令並不會馬上執行,而是放在隊列中,只有在事務提交后才會執行。
但是,要注意注意:redis中,如果遇到格式正確而數據類型不符合的情況時,不會進行事務回滾。這是什么意思,如下圖操作所示:
問題描述:比如我要保存1000金額到內存中,但是我不小心將金額輸入成1000a,后面多了一個a。但是同樣保存到了隊列中。最后當提交事務的時候便會報錯,可是1000a還是保存到了內存,證明事務並沒有回滾。
redis中存在監聽機制,可以監聽某一個key。
/**
* redis的事務管理,要保證事務的開啟和提交是同一條連接。
*/
@Test
public void transcation(){
List results = (List) redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
//開啟事務
redisOperations.multi();
//進行操作
redisOperations.opsForValue().set("name", "張三");
redisOperations.opsForValue().get("name");
//提交事務
List result = redisOperations.exec();
return result;
}
});
System.out.println("-->" + results);
}
5.2,流水線
在現實情況中,redis的讀寫速度十分快,而系統的瓶頸往往是在網絡通信中的延遲。redis可能會再很多時候處於空閑狀態而等待命令的到達。為了解決這個問題,可以使用redis的流水線,流水線是一種通訊協議,類似一個隊列批量執行一組命令。
由於這種情況在實際工作中較少使用,所以就簡短介紹一下。
/**
* 流水線
*/
@Test
public void pipelined(){
//流水線
long begin = System.currentTimeMillis();
redisTemplate.executePipelined(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
for (int i = 0; i < 100000; i++) {
redisOperations.opsForValue().set("key" + i, "value" + i);
redisOperations.opsForValue().get("key" + i);
}
return null;
}
});
long end = System.currentTimeMillis();
System.out.println("耗時:" + (end - begin));
}
5.3,發布訂閱
說到發布訂閱是否會想到RabbitMQ等消息中間件?
但是redis的發布訂閱具有實時性,當發布者改變數據時,訂閱者便會接收到更改后的消息。
使用命令:
subscribe chat:訂閱chat渠道。
publish chat "message:發布消息到chat渠道。
5.3.1,定義監聽類:
/**
* Redis消息監聽器
*/
public class RedisMessageListener implements MessageListener{
private RedisTemplate template;
@Override
public void onMessage(Message message, byte[] pattern) {
//獲取渠道名稱
System.out.println("渠道名稱:" + new String(pattern));
//獲得消息
byte[] body = message.getBody();
//獲得值序列化轉換器
String msg = (String) template.getValueSerializer().deserialize(body);
System.out.println("消息為:" + msg);
}
public RedisTemplate getTemplate() {
return template;
}
public void setTemplate(RedisTemplate template) {
this.template = template;
}
}
5.3.2,配置文件配置監聽
<!-- 配置監聽器 -->
<bean id="redisMsgListener" class="com.yx.redis.RedisMessageListener">
<!-- 注入redis模板 -->
<property name="template" ref="template"></property>
</bean>
<!-- 配置監聽容器 -->
<bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer" destroy-method="destroy">
<!-- redis連接工廠 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 配置連接池,連接池生存才能持續監聽 -->
<property name="taskExecutor">
<bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="3"/>
</bean>
</property>
<!-- 配置消息監聽 -->
<property name="messageListeners">
<map>
<!-- key-ref和監聽器的id保持一致 -->
<entry key-ref="redisMsgListener">
<bean class="org.springframework.data.redis.listener.ChannelTopic">
<!-- 定義渠道 -->
<constructor-arg value="yx"/>
</bean>
</entry>
</map>
</property>
</bean>
5.3.3,發布消息
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-*.xml");
RedisTemplate redistemp = context.getBean(RedisTemplate.class);
redistemp.convertAndSend("yx", "Hello");
5.4,超時時間
在redis中的超時時間時非常重要的,因為我們的內存時有限的,在一段時間內如果沒有對一些數據進行處理。那便會產生很多的垃圾數據,因此對數據進行時間 上的設置是一種較好的習慣。
這里先暫時不講述過期時間的原理,后面會與大家分享,還請關注哦~~~
超時時間的命令:
persist key:持久化key,即得永生(移除key的超時時間)。
expire key seconds:設置超時時間,單位為秒。
ttl key:查看key的超時時間,單位為秒,返回-1表示沒有超時時間,如果key不存在或者已經超時,則返回-2。
pttl key:查看key的超時時間,單位為毫秒。
pexpire key milliseconds:設置key的超時時間,以毫秒為單位。
關於超時時間需要有幾點注意:當一個key過了超時時間以后,並不會立刻從內存中移除。在以下情況下數據會被清除。
1、當要獲得key的值的時候,比如執行了get key命令。
2、系統自己會有一個定時器,每隔1秒,掃描一次內存。清除超時的key(不會完全掃描所有的key,不會完全的移除所有超時的key)。
3、內存已滿,就會根據配置文件進行內存數據的清理。
@Test
public void expire() throws ParseException {
redisTemplate.opsForValue().set("name", "小明");
//設置超時時間 - 5 ~ 10分鍾
redisTemplate.expire("name", 10, TimeUnit.SECONDS);
//設置超時時間到指定的時間
String time = "2019-08-23 12:00:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(time);
redisTemplate.expireAt("name", date);
//移除超時時間
redisTemplate.persist("name");
//獲得還能活多久
redisTemplate.getExpire("name");
String name = (String) redisTemplate.opsForValue().get("name");
System.out.println(name);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name);
}
六,總結
寫到這里redis的基本使用也差不多了,但是仍有很多技術點沒有記錄到。比如與Lua語言的結合使用,redis的持久化,內存的淘汰策略,讀寫分離(哨兵模式),集群等。
如果你看到這篇博客,以上沒有分享的內容會在后續發布的,還請關注~
最后,以上內容均是自主學習的總結,如有錯誤或者不合適的地方歡迎留言(或者郵箱)指教。
感謝觀看!