首先數據庫分爲關係型數據庫和非關係型數據庫,關係型數據庫是採用關係模型來組織數據的數據庫,簡單來講就是二維表格模型,同時保證事務的一致性。html
相反非關係型數據庫採用key-value形式進行存儲,是一種數據結構化存儲方法的集合,具備分佈式性質。java
Redis是當前比較熱門的NOSQL系統之一,它是一個開源的使用ANSI c語言編寫的key-value存儲系統(區別於MySQL的二維表格的形式存儲。)遵照BSD協議、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。它一般被稱爲數據結構服務器,由於值(value)能夠是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型。node
1,性能快:redis讀取的速度是110000次/s,寫的速度是81000次/s。mysql
2,豐富的數據類型:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)等。jquery
3,原子性:Redis的全部操做都是原子性的,且多個客戶端同時訪問redis客戶端可得到更新後的值。c++
4,持久化:集羣(主從複製,分佈式)。面試
1 、redis不只僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲類型。memcache支持簡單的數據類型,String,同時還能夠緩存圖片,視頻。redis
2 、Redis支持數據的備份,即master-slave模式的數據備份(主從複製)。spring
3 、Redis支持數據的持久化,能夠將內存中的數據保持在磁盤中,重啓的時候能夠再次加載進行使用。sql
四、 redis的速度比memcached快不少
五、Memcached是多線程,非阻塞IO複用的網絡模型;Redis使用單線程的IO複用模型。
6,數據安全性:memcache掛掉後,數據便消失;redis能夠按期保存到磁盤(持久化)。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
注意:redis默認使用JDK序列化方式
更多詳細命令,請參考Redis中文網:https://www.redis.net.cn/
/** * 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); }
/** * 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); }
/** * 鏈表數據結構 */ @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); }
/** * 集合操做 */ @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); }
/** * 有序集合 */ @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); }
與其餘NoSQL不一樣,Redis是存在事務的,儘管沒有數據庫那麼強大,可是仍是很是有用,尤爲是在高併發的狀況中,使用redis的事務能夠保證數據一致性的同時,大幅度提升數據讀寫的響應速度。
redis的事務是使用multi-exec的命令組合,使用它能夠提供兩個重要保證:
一、事務是一個被隔離的操做,事務中的方法都會被redis進行序列化並按順序執行,事務在執行的過程當中不會被其餘客戶端的發出的命令所打斷。
二、事務是一個原子性操做,它要麼所有執行、要麼所有不執行。
==事務的經常使用命令:==
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); }
在現實狀況中,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)); }
說到發佈訂閱是否會想到RabbitMQ等消息中間件?
可是redis的發佈訂閱具備實時性,當發佈者改變數據時,訂閱者便會接收到更改後的消息。
==使用命令:==
subscribe chat:訂閱chat渠道。
publish chat "message:發佈消息到chat渠道。
/** * 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; } }
<!-- 配置監聽器 --> <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>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-*.xml"); RedisTemplate redistemp = context.getBean(RedisTemplate.class); redistemp.convertAndSend("yx", "Hello");
在redis中的超時時間時很是重要的,由於咱們的內存時有限的,在一段時間內若是沒有對一些數據進行處理。那便會產生不少的垃圾數據,所以對數據進行時間 上的設置是一種較好的習慣。
這裏先暫時不講述過時時間的原理,後面會與你們分享,還請關注哦~~~
==超時時間的命令:==
persist key:持久化key,即得永生(移除key的超時時間)。
expire key seconds:設置超時時間,單位爲秒。
ttl key:查看key的超時時間,單位爲秒,返回-1表示沒有超時時間,若是key不存在或者已經超時,則返回-2。
pttl key:查看key的超時時間,單位爲毫秒。
pexpire key milliseconds:設置key的超時時間,以毫秒爲單位。
==關於超時時間須要有幾點注意:當一個key過了超時時間之後,並不會馬上從內存中移除。在如下狀況下數據會被清除。==
一、當要得到key的值的時候,好比執行了get key命令。
二、系統本身會有一個定時器,每隔1秒,掃描一次內存。清除超時的key(不會徹底掃描全部的key,不會徹底的移除全部超時的key)。
三、內存已滿,就會根據配置文件進行內存數據的清理。
@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的持久化,內存的淘汰策略,讀寫分離(哨兵模式),集羣等。
若是你看到這篇博客,以上沒有分享的內容會在後續發佈的,還請關注~
最後,以上內容均是自主學習的總結,若有錯誤或者不合適的地方歡迎留言(或者郵箱)指教。
感謝觀看!