Redis
Redis入門
? ?Redis是一個(gè)開源(BSD許可),內(nèi)存存儲的數(shù)據(jù)結(jié)構(gòu)服務(wù)器,可用作數(shù)據(jù)庫,高速緩存和消息隊(duì)列代理。它支持字符串、哈希表、列表、集合、有序集合,位圖,hyperloglogs等數(shù)據(jù)類型。內(nèi)置復(fù)制、Lua腳本、LRU收回、事務(wù)以及不同級別磁盤持久化功能,同時(shí)通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區(qū)
Redis下載
下載地址:http://download.redis.io/releases/redis-3.2.8.tar.gz
Redis的安裝
? ?這里在mac下模擬安裝,如果需要在linux或者window下安裝的話,請自行百度。這里以3.2.8為例
Redis的啟動和關(guān)閉
啟動:
? ?在命令行輸入命令 redis-server,默認(rèn)鏈接Redis(127.0.0.1:6379) 啟動,如果出現(xiàn)蛋糕圖案說明安裝成功。
關(guān)閉:
- 使用redis-cli shutdown命令關(guān)閉。
- 使用進(jìn)程ps -ef | grep redis , kill -9 pid,使用這種方式可能會導(dǎo)致數(shù)據(jù)丟失。
Redis鍵
常用命令:
- key * :返回所有鍵
- exists key : 判斷某個(gè)key是否存在
- expire key : 給指定的鍵設(shè)置過期時(shí)間
- ttl key : 查看還有多少秒過期,-1表示永不過期,-2表示已過期
- type key : 查看你的key是什么類型
Redis數(shù)據(jù)類型
- 字符串
? ?string類型是二進(jìn)制安全的。意思是redis的string可以包含任何數(shù)據(jù)。比如jpg圖片或者序列化的對象。
string類型是Redis最基本的數(shù)據(jù)類型,一個(gè)鍵最大能存儲512MB??梢栽赾onf文件里設(shè)置maxmemory的內(nèi)存大小,單位是字節(jié)。
127.0.0.1:6379> set key1 hello
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379>
如果key已經(jīng)存在則覆蓋已經(jīng)存在的值。
- Hash(哈希)
hset key filed value
hget key filed
hmset key filed value [filed value...]
hmget key filed [filed...]
hgetall key
127.0.0.1:6379> hmset user:1 usernmae zhangsan password 123456 age 18 address xian
OK
127.0.0.1:6379> hgetall user:1
1) "usernmae"
2) "zhangsan"
3) "password"
4) "123456"
5) "age"
6) "18"
7) "address"
8) "xian"
127.0.0.1:6379>
127.0.0.1:6379> hmget user:1 age
1) "18"
- List(列表)
Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個(gè)元素到 列表的頭部(左邊)或者尾部(右邊)。
127.0.0.1:6379> lpush lisi xianren
(integer) 1
127.0.0.1:6379> lpush lisi 18
(integer) 2
127.0.0.1:6379> lpush lisi qinguo
(integer) 3
127.0.0.1:6379> lrange lisi 0 10
1) "qinguo"
2) "18"
3) "xianren"
127.0.0.1:6379>
- Set(集合)
Redis的Set是string類型的無序集合。
127.0.0.1:6379> sadd redis hao haoyong henhaoyong
(integer) 3
127.0.0.1:6379> sadd redis shizhendehaoyong
(integer) 1
127.0.0.1:6379> smembers redis
1) "hao"
2) "haoyong"
3) "henhaoyong"
4) "shizhendehaoyong"
127.0.0.1:6379>
- zset有序集合
Redis zset和set一樣也是string類型元素的集合,且不允許重復(fù)的成員。不同的是每個(gè)元素都會關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù)。redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。
127.0.0.1:6379> zadd wangwu 1 a
(integer) 1
127.0.0.1:6379> zadd wangwu 0 b
(integer) 1
127.0.0.1:6379> zadd wangwu 3 c
(integer) 1
127.0.0.1:6379> zadd wagnwu 2 d
(integer) 1
127.0.0.1:6379> zrangebyscore wangwu 0 5
1) "b"
2) "a"
3) "c"
127.0.0.1:6379>
解析配置文件
- daemonize no
默認(rèn)情況下 redis不是作為守護(hù)進(jìn)程運(yùn)行的,如果你想讓它在后臺運(yùn)行,你就把它改成yes。當(dāng)redis作為守護(hù)進(jìn)程運(yùn)行的時(shí)候,它會寫一個(gè) pid 到 /var/run/redis.pid 文件里面。 - tcp-backlog 511
在高并發(fā)的環(huán)境下,你需要把這個(gè)值調(diào)高以避免客戶端連接緩慢的問題。
Linux內(nèi)核會一聲不響的把這個(gè)值縮小成/proc/sys/net/core/somaxconn 對應(yīng)的值,所以你要修改這兩個(gè)值才能達(dá)到你的預(yù)期。 - tcp-keepalive 300
- loglevel notice
- databases 默認(rèn)值16,表示Redis默認(rèn)的數(shù)據(jù)庫實(shí)力數(shù)量,如果需要切換數(shù)據(jù)庫,需要使用select number命令切換,flushall清空整個(gè)Redis服務(wù)器的數(shù)據(jù),包括0-16的所有數(shù)據(jù)庫實(shí)例,所以使用謹(jǐn)慎使用此命令。flushdb用于清空當(dāng)前數(shù)據(jù)庫中所有的key。
- SNAPSHOTTING
持久換 - SECURITY
默認(rèn)redis是不需要密碼輸入的,如果我們需要設(shè)置密碼登錄,可以通過命令config set requirepass "111111" ,但是下次在使用命令的時(shí)候回提示你輸入密碼,但是redis是運(yùn)行于linux之上的,Redis認(rèn)為Linux是足夠安全的,所以redis默認(rèn)是不需要密碼的。Redis支持密碼,但不建議這么做。 - LIMISTS限制
- 1 maxClients
- 2 maxmemory
- 3 maxmemory-policy(緩存過期策略6種)
- volatile-lru -> remove the key with an expire set using an LRU algorithm
- allkeys-lru -> remove any key according to the LRU algorithm
- volatile-random -> remove a random key with an expire set
- allkeys-random -> remove a random key, any key
- volatile-ttl -> remove the key with the nearest expire time (minor TTL)
- noeviction -> don't expire at all, just return an error on write operations
- 4 maxmemroy-samples
設(shè)置樣本數(shù)量,LRU算法和TTL算法都并非是精準(zhǔn)的算法而是估算值,,所以你可以設(shè)置樣本的大小,Redis默認(rèn)會檢查這么多個(gè)key并選擇其中LRU的那個(gè)
查看數(shù)據(jù)庫大小
使用info命令查看數(shù)據(jù)庫使用情況
used_memory:數(shù)據(jù)占用了多少內(nèi)存(字節(jié)
used_memory_human:數(shù)據(jù)占用了多少內(nèi)存(帶單位的,可讀性好)
used_memory_rss:redis占用了多少內(nèi)存
used_memory_peak:占用內(nèi)存的峰值(字節(jié))
used_memory_peak_human:占用內(nèi)存的峰值(帶單位的,可讀性好)
127.0.0.1:6379> info
......
......
# Memory
used_memory:886548
used_memory_human:865.77K
used_memory_rss:2068480
used_memory_rss_human:1.97M
used_memory_peak:886548
used_memory_peak_human:865.77K
used_memory_peak_perc:100.12%
used_memory_overhead:884987
used_memory_startup:835253
used_memory_dataset:1561
used_memory_dataset_perc:3.04%
......
......
Redis的持久化
- rdb
實(shí)現(xiàn)機(jī)制:當(dāng)redis需要做持久化時(shí),redis會fork一個(gè)子進(jìn)程;子進(jìn)程將數(shù)據(jù)寫到磁盤上一個(gè)臨時(shí)RDB文件中;當(dāng)子進(jìn)程完成寫臨時(shí)文件后,將原來的RDB替換掉,這樣的好處就是可以copy-on-write - aof
filesnapshotting方法在redis異常死掉時(shí),最近的數(shù)據(jù)會丟失,Append-only方法可以做到全部數(shù)據(jù)不丟失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中開啟(默認(rèn)是no),appendonly yes開啟AOF之后,redis每執(zhí)行一個(gè)修改數(shù)據(jù)的命令,都會把它添加到aof文件中,當(dāng)redis重啟時(shí),將會讀取AOF文件進(jìn)行“重放”以恢復(fù)到redis關(guān)閉前的最后時(shí)刻。
Redis的事務(wù)
? ?MULTI 命令用于開啟一個(gè)事務(wù),它總是返回 OK。 MULTI 執(zhí)行之后, 客戶端可以繼續(xù)向服務(wù)器發(fā)送任意多條命令, 這些命令不會立即被執(zhí)行, 而是被放到一個(gè)隊(duì)列中, 當(dāng) EXEC命令被調(diào)用時(shí), 所有隊(duì)列中的命令才會被執(zhí)行。
另一方面, 通過調(diào)用 DISCARD , 客戶端可以清空事務(wù)隊(duì)列, 并放棄執(zhí)行事務(wù)
demo如下
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr foo
QUEUED
127.0.0.1:6379> incr bar
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379>
發(fā)生回滾的的原因
- 語法錯(cuò)誤
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key value
QUEUED
127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> errorcommand key
(error) ERR unknown command 'errorcommand'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379>
? ?當(dāng)遇到語法錯(cuò)誤的時(shí)候,后面執(zhí)行exec的命令時(shí),系統(tǒng)會discarded事務(wù)。
- 運(yùn)行錯(cuò)誤
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> sadd key 2
QUEUED
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get key
"3"
127.0.0.1:6379>
-
為什么不支持事務(wù)
以下是這種做法的優(yōu)點(diǎn):- Redis 命令只會因?yàn)殄e(cuò)誤的語法而失?。ú⑶疫@些問題不能在入隊(duì)時(shí)發(fā)現(xiàn)),或是命令用在了錯(cuò)誤類型的鍵上面:這也就是說,從實(shí)用性的角度來說,失敗的命令是由編程錯(cuò)誤造成的,而這些錯(cuò)誤應(yīng)該在開發(fā)的過程中被發(fā)現(xiàn),而不應(yīng)該出現(xiàn)在生產(chǎn)環(huán)境中。
- 因?yàn)椴恍枰獙貪L進(jìn)行支持,所以 Redis 的內(nèi)部可以保持簡單且快速。
Redis的事務(wù)沒有關(guān)系數(shù)據(jù)庫事務(wù)提供的回滾(rollback)功能 。為此開發(fā)者必須在事務(wù)執(zhí)行出錯(cuò)后自己收拾剩下的攤子(將數(shù)據(jù)庫復(fù)原回事務(wù)執(zhí)行前的狀態(tài)等)。
放棄事務(wù)
使用discard命令,放棄事務(wù),事務(wù)隊(duì)列被清空,并且客戶端會從事務(wù)狀態(tài)中退出watch命令
watch監(jiān)控一個(gè)鍵的值是否被改過,在一個(gè)事務(wù)中,在沒有執(zhí)行exec命令之前,如果監(jiān)控的值被改動了,則事務(wù)失敗。如果事務(wù)成功,則取消對所有的鍵的監(jiān)控。watch也可以監(jiān)視多個(gè)值watch key1 key2 key3
Redis 分區(qū)
? ?分區(qū)可以讓Redis管理更大的內(nèi)存,Redis將可以使用所有機(jī)器的內(nèi)存。如果沒有分區(qū),你最多只能使用一臺機(jī)器的內(nèi)存。
分區(qū)使Redis的計(jì)算能力通過簡單地增加計(jì)算機(jī)得到成倍提升,Redis的網(wǎng)絡(luò)帶寬也會隨著計(jì)算機(jī)和網(wǎng)卡的增加而成倍增長。
- Redis集群
? ?
Redis集群是自動分片和高可用的首選方案 - Twemproxy
Twemproxy是Twitter維護(hù)的(緩存)代理系統(tǒng),代理Memcached的ASCII協(xié)議和Redis協(xié)議。該代理實(shí)現(xiàn)了一致性hash算法。
Redis的訂閱發(fā)布
Redis的復(fù)制
Redis的Java客戶端Jedis
Spring Boot整合Redis
yml配置文件
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
pool:
max-active: 8 # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
max-wait: -1 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制
max-idle: 8 # 連接池中的最大空閑連接
min-idle: 0 # 連接池中的最小空閑連接
timeout: 0 # 鏈接超時(shí)時(shí)間
ServiceImpl.java如下
package com.yao.service;
import com.yao.domain.City;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
/**
* Created by yaochenglong on 17/11/20.
* CityServiceImpl demo
*/
@Service
public class CityServiceImpl implements CityService {
private static Logger logger = Logger.getLogger(CityServiceImpl.class);
@Autowired
private RedisTemplate redisTemplate;
@Override
public City findCityById(String id) {
String key ="city"+id;
ValueOperations<String,City> operations = redisTemplate.opsForValue();
Boolean hasKey = redisTemplate.hasKey(key);
if(hasKey){
City city = operations.get(key);
logger.info("CityServiceImpl.findCityById() : 從緩存中獲取了城市>>"+city.getCityName());
return city;
}
//模擬從數(shù)據(jù)庫中獲取數(shù)據(jù)
City city = new City();
city.setId("123");
city.setCityName("西安");
city.setDescription("愛我大西安");
operations.set(key,city);
System.out.println("ityServiceImpl.findCityById() : 從數(shù)據(jù)庫中獲取了城市");
return city;
}
@Override
public Long updateCity(City city) {
//Long ret = cityDao.updateCity(city);
Long ret = 1l;
// 緩存存在,刪除緩存
String key = "city_" + city.getId();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
logger.info("CityServiceImpl.updateCity() : 從緩存中刪除城市 >> " + city.toString());
}
return ret;
}
}
Controller 如下
package com.yao.controller;
import com.yao.domain.City;
import com.yao.service.CityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by yaochenglong on 17/11/20.
*/
@RestController
public class CityController {
@Autowired
private CityService cityService;
@GetMapping("/city/get/{id}")
public String getCity(@PathVariable("id") String id){
City city = cityService.findCityById(id);
return city.getCityName();
}
}
連續(xù)訪問請求兩次,結(jié)果如下:
2017-11-20 22:19:59.985 INFO 1179 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-11-20 22:19:59.985 INFO 1179 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-11-20 22:19:59.995 INFO 1179 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 10 ms
ityServiceImpl.findCityById() : 從數(shù)據(jù)庫中獲取了城市
2017-11-20 22:20:01.704 INFO 1179 --- [nio-8080-exec-3] com.yao.service.CityServiceImpl : CityServiceImpl.findCityById() : 從緩存中獲取了城市>>西安
? ?我們可以看到第一次從數(shù)據(jù)庫中獲取數(shù)據(jù),第二次訪問則從緩存中獲取數(shù)據(jù)。
高可用性
Redis哨兵
自動分區(qū)