日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

一、鍵值設(shè)計

1. key名設(shè)計

(1)【建議】: 可讀性和可管理性

以業(yè)務(wù)名(或數(shù)據(jù)庫名)為前綴(防止key沖突),用冒號分隔,比如業(yè)務(wù)名:表名:id

阿里云的redis規(guī)范

 

(2)【建議】:簡潔性

保證語義的前提下,控制key的長度,當(dāng)key較多時,內(nèi)存占用也不容忽視,例如:

阿里云的redis規(guī)范

 

(3)【強制】:不要包含特殊字符

反例:包含空格、換行、單雙引號以及其他轉(zhuǎn)義字符

2. value設(shè)計

(1)【強制】:拒絕bigkey(防止網(wǎng)卡流量、慢查詢)

string類型控制在10KB以內(nèi),hash、list、set、zset元素個數(shù)不要超過5000。

反例:一個包含200萬個元素的list。

非字符串的bigkey,不要使用del刪除,使用hscan、sscan、zscan方式漸進式刪除,同時要注意防止bigkey過期時間自動刪除問題(例如一個200萬的zset設(shè)置1小時過期,會觸發(fā)del操作,造成阻塞,而且該操作不會不出現(xiàn)在慢查詢中(latency可查)),查找方法和刪除方法

(2)【推薦】:選擇適合的數(shù)據(jù)類型。

例如:實體類型(要合理控制和使用數(shù)據(jù)結(jié)構(gòu)內(nèi)存編碼優(yōu)化配置,例如ziplist,但也要注意節(jié)省內(nèi)存和性能之間的平衡)

反例:

阿里云的redis規(guī)范

 

正例:

阿里云的redis規(guī)范

 

(3)【推薦】:控制key的生命周期,redis不是垃圾桶。

建議使用expire設(shè)置過期時間(條件允許可以打散過期時間,防止集中過期),不過期的數(shù)據(jù)重點關(guān)注idletime。

二、命令使用

1.【推薦】 O(N)命令關(guān)注N的數(shù)量

例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明確N的值。有遍歷的需求可以使用hscan、sscan、zscan代替。

2.【推薦】:禁用命令

禁止線上使用keys、flushall、flushdb等,通過redis的rename機制禁掉命令,或者使用scan的方式漸進式處理。

3.【推薦】合理使用select

redis的多數(shù)據(jù)庫較弱,使用數(shù)字進行區(qū)分,很多客戶端支持較差,同時多業(yè)務(wù)用多數(shù)據(jù)庫實際還是單線程處理,會有干擾。

4.【推薦】使用批量操作提高效率

原生命令:例如mget、mset。

非原生命令:可以使用pipeline提高效率。

但要注意控制一次批量操作的元素個數(shù)(例如500以內(nèi),實際也和元素字節(jié)數(shù)有關(guān))。

注意兩者不同:

  1. 原生是原子操作,pipeline是非原子操作。
  2. pipeline可以打包不同的命令,原生做不到
  3. pipeline需要客戶端和服務(wù)端同時支持。

5.【建議】Redis事務(wù)功能較弱,不建議過多使用

Redis的事務(wù)功能較弱(不支持回滾),而且集群版本(自研和官方)要求一次事務(wù)操作的key必須在一個slot上(可以使用hashtag功能解決)

6.【建議】Redis集群版本在使用Lua上有特殊要求:

1.所有key都應(yīng)該由 KEYS 數(shù)組來傳遞,redis.call/pcall 里面調(diào)用的redis命令,key的位置,必須是KEYS array, 否則直接返回

error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array"

2.所有key,必須在1個slot上,否則直接返回

error, "-ERR eval/evalsha command keys must in same slot" 

7.【建議】必要情況下使用monitor命令時,要注意不要長時間使用。

三、客戶端使用

1.【推薦】

避免多個應(yīng)用使用一個Redis實例

正例:不相干的業(yè)務(wù)拆分,公共數(shù)據(jù)做服務(wù)化。

2.【推薦】

使用帶有連接池的數(shù)據(jù)庫,可以有效控制連接,同時提高效率:

3.【建議】

高并發(fā)下建議客戶端添加熔斷功能(例如netflix hystrix)

4.【推薦】

設(shè)置合理的密碼,如有必要可以使用SSL加密訪問(阿里云Redis支持)

5.【建議】

根據(jù)自身業(yè)務(wù)類型,選好maxmemory-policy(最大內(nèi)存淘汰策略),設(shè)置好過期時間。

默認(rèn)策略是volatile-lru,即超過最大內(nèi)存后,在過期鍵中使用lru算法進行key的剔除,保證不過期數(shù)據(jù)不被刪除,但是可能會出現(xiàn)OOM問題。

其他策略如下:

allkeys-lru:根據(jù)LRU算法刪除鍵,不管數(shù)據(jù)有沒有設(shè)置超時屬性,直到騰出足夠空間為止。

allkeys-random:隨機刪除所有鍵,直到騰出足夠空間為止。

volatile-random:隨機刪除過期鍵,直到騰出足夠空間為止。

volatile-ttl:根據(jù)鍵值對象的ttl屬性,刪除最近將要過期數(shù)據(jù)。如果沒有,回退到noeviction策略。

noeviction:不會剔除任何數(shù)據(jù),拒絕所有寫入操作并返回客戶端錯誤信息"(error) OOM command not allowed when used memory",此時Redis只響應(yīng)讀操作。

bigkey

【強制】:拒絕bigkey(防止網(wǎng)卡流量、慢查詢)

string類型控制在10KB以內(nèi),hash、list、set、zset元素個數(shù)不要超過5000。

反例:一個包含200萬個元素的list。

非字符串的bigkey,不要使用del刪除,使用hscan、sscan、zscan方式漸進式刪除,同時要注意防止bigkey過期時間自動刪除問題(例如一個200萬的zset設(shè)置1小時過期,會觸發(fā)del操作,造成阻塞,而且該操作不會出現(xiàn)在慢查詢中(latency可查))

一、什么是bigkey

在Redis中,一個字符串最大512MB,一個二級數(shù)據(jù)結(jié)構(gòu)(例如hash、list、set、zset)可以存儲大約40億個(2^32-1)個元素,但實際上中如果下面兩種情況,我就會認(rèn)為它是bigkey。

  1. 字符串類型:它的big體現(xiàn)在單個value值很大,一般認(rèn)為超過10KB就是bigkey。
  2. 非字符串類型:哈希、列表、集合、有序集合,它們的big體現(xiàn)在元素個數(shù)太多。

二、危害

bigkey可以說就是Redis的老鼠屎,具體表現(xiàn)在:

1.內(nèi)存空間不均勻:這樣會不利于集群對內(nèi)存的統(tǒng)一管理,存在丟失數(shù)據(jù)的隱患。

2.超時阻塞:由于Redis單線程的特性,操作bigkey的通常比較耗時,也就意味著阻塞Redis可能性越大,這樣會造成客戶端阻塞或者引起故障切換,它們通常出現(xiàn)在慢查詢中。

例如,在Redis發(fā)現(xiàn)了這樣的key,你就等著DBA找你吧。。

127.0.0.1:6379> hlen big:hash(integer) 
2000000
127.0.0.1:6379> hgetall big:hash
1) "a"
2) "1"
此處省略400萬行

3.網(wǎng)絡(luò)擁塞:

bigkey也就意味著每次獲取要產(chǎn)生的網(wǎng)絡(luò)流量較大,假設(shè)一個bigkey為1MB,客戶端每秒訪問量為1000,那么每秒產(chǎn)生1000MB的流量,對于普通的千兆網(wǎng)卡(按照字節(jié)算是128MB/s)的服務(wù)器來說簡直是滅頂之災(zāi),而且一般服務(wù)器會采用單機多實例的方式來部署,也就是說一個bigkey可能會對其他實例造成影響,其后果不堪設(shè)想。

4.過期刪除:

有個bigkey,它安分守己(只執(zhí)行簡單的命令,例如hget、lpop、zscore等),但它設(shè)置了過期時間,當(dāng)它過期后,會被刪除,如果沒有使用Redis 4.0的過期異步刪除(lazyfree-lazy-expire yes),就會存在阻塞Redis的可能性,而且這個過期刪除不會從主節(jié)點的慢查詢發(fā)現(xiàn)(因為這個刪除不是客戶端產(chǎn)生的,是內(nèi)部循環(huán)事件,可以從latency命令中獲取或者從slave節(jié)點慢查詢發(fā)現(xiàn))。

5.遷移困難:

當(dāng)需要對bigkey進行遷移(例如Redis cluster的遷移slot),實際上是通過migrate命令來完成的,migrate實際上是通過dump + restore + del三個命令組合成原子命令完成,如果是bigkey,可能會使遷移失敗,而且較慢的migrate會阻塞Redis。

三、怎么產(chǎn)生的?

一般來說,bigkey的產(chǎn)生都是由于程序設(shè)計不當(dāng),或者對于數(shù)據(jù)規(guī)模預(yù)料不清楚造成的,來看幾個:

(1) 社交類:粉絲列表,如果某些明星或者大v不精心設(shè)計下,必是bigkey。

(2) 統(tǒng)計類:例如按天存儲某項功能或者網(wǎng)站的用戶集合,除非沒幾個人用,否則必是bigkey。

(3) 緩存類:將數(shù)據(jù)從數(shù)據(jù)庫load出來序列化放到Redis里,這個方式非常常用,但有兩個地方需要注意,第一,是不是有必要把所有字段都緩存,第二,有沒有相關(guān)關(guān)聯(lián)的數(shù)據(jù)。

例如我之前遇到過一個例子,該同學(xué)將某明星一個專輯下所有視頻信息都緩存一個巨大的json中,造成這個json達到6MB,后來這個明星發(fā)了一個官宣。。。這個我就不多說了,領(lǐng)盒飯去吧。

四、如何發(fā)現(xiàn)

1.redis-cli --bigkeys

redis-cli提供了--bigkeys來查找bigkey,例如下面就是一次執(zhí)行結(jié)果:

-------- summary -------
Biggest string found 'user:1' has 5 bytes
Biggest list found 'taskflow:175448' has 97478 items
Biggest set found 'redisServerSelect:set:11597' has 49 members
Biggest hash found 'loginUser:t:20180905' has 863 fields
Biggest zset found 'hotkey:scan:instance:zset' has 3431 members
40 strings with 200 bytes (00.00% of keys, avg size 5.00)
2747619 lists with 14680289 items (99.86% of keys, avg size 5.34)
2855 sets with 10305 members (00.10% of keys, avg size 3.61)
13 hashs with 2433 fields (00.00% of keys, avg size 187.15)
830 zsets with 14098 members (00.03% of keys, avg size 16.99)

可以看到--bigkeys給出了每種數(shù)據(jù)結(jié)構(gòu)的top 1 bigkey,同時給出了每種數(shù)據(jù)類型的鍵值個數(shù)以及平均大小。

--bigkeys對問題的排查非常方便,但是在使用它時候也有幾點需要注意。

  1. 建議在從節(jié)點執(zhí)行,因為--bigkeys也是通過scan完成的。
  2. 建議在節(jié)點本機執(zhí)行,這樣可以減少網(wǎng)絡(luò)開銷。
  3. 如果沒有從節(jié)點,可以使用--i參數(shù),例如(--i 0.1 代表100毫秒執(zhí)行一次)
  4. --bigkeys只能計算每種數(shù)據(jù)結(jié)構(gòu)的top1,如果有些數(shù)據(jù)結(jié)構(gòu)非常多的bigkey,也搞不定,畢竟不是自己寫的東西嘛
  5. debug object

再來看一個場景:

你好,麻煩幫我查一下Redis里大于10KB的所有key

您好,幫忙查一下Redis中長度大于5000的hash key

是不是發(fā)現(xiàn)用--bigkeys不行了(當(dāng)然如果改源碼也不是太難),但有沒有更快捷的方法,Redis提供了debug object ${key}命令獲取鍵值的相關(guān)信息:

127.0.0.1:6379> hlen big:hash
(integer) 5000000
127.0.0.1:6379> debug object big:hash
Value at:0x7fda95b0cb20 refcount:1 encoding:hashtable serializedlength:87777785 lru:9625559 lru_seconds_idle:2
(1.08s)

其中serializedlength表示key對應(yīng)的value序列化之后的字節(jié)數(shù),當(dāng)然如果是字符串類型,完全看可以執(zhí)行strlen,例如:

127.0.0.1:6379> strlen key
(integer) 947394

這樣你就可以用scan + debug object的方式遍歷Redis所有的鍵值,找到你需要閾值的數(shù)據(jù)了。

但是在使用debug object時候一定要注意以下幾點:

(1) debug object bigkey本身可能就會比較慢,它本身就會存在阻塞Redis的可能

(2) 建議在從節(jié)點執(zhí)行

(3) 建議在節(jié)點本地執(zhí)行

(4) 如果不關(guān)系具體字節(jié)數(shù),完全可以使用scan + strlen|hlen|llen|scard|zcard替代,他們都是o(1)

3 memory usage

上面的debug object可能會比較危險、而且不太準(zhǔn)確(序列化后的長度),有沒有更準(zhǔn)確的呢?Redis 4.0開始提供memory usage命令可以計算每個鍵值的字節(jié)數(shù)(自身、以及相關(guān)指針開銷,具體的細(xì)節(jié)后面有文章會分析),例如下面是一次執(zhí)行結(jié)果:

127.0.0.1:6379> memory usage big:hash
(integer) 318663444

下面我們來對比就可以看出來,當(dāng)前系統(tǒng)就一個key,總內(nèi)存消耗是400MB左右,memory usage相比debug object還是要精確一些的。

127.0.0.1:6379> dbsize
(integer) 1
127.0.0.1:6379> hlen big:hash
(integer) 5000000
#約300MB
127.0.0.1:6379> memory usage big:hash
(integer) 318663444
#約85MB
127.0.0.1:6379> debug object big:hash
Value at:0x7fda95b0cb20 refcount:1 encoding:hashtable serializedlength:87777785 lru:9625814 lru_seconds_idle:9
(1.06s)
127.0.0.1:6379> info memory
# Memory
used_memory_human:402.16M

如果你使用Redis 4.0+,你就可以用scan + memory usage(pipeline)了,而且很好的一點是,memory不會執(zhí)行很慢,當(dāng)然依然是建議從節(jié)點 + 本地 。

4.客戶端

上面三種方式都有一個問題,就是馬后炮,如果想很實時的找到bigkey,一方面你可以試試修改Redis源碼,還有一種方式就是可以修改客戶端,以jedis為例,可以在關(guān)鍵的出入口加上對應(yīng)的檢測機制,例如以Jedis的獲取結(jié)果為例子:

protected Object readProtocolWithCheckingBroken() {
	Object o = null;
	try {
		o = Protocol.read(inputStream);
		return o;
	}catch(JedisConnectionException exc) {
		UsefulDataCollector.collectException(exc, getHostPort(), System.currentTimeMillis());
		broken = true;
		throw exc;
	}finally {
		if(o != null) {
			if(o instanceof byte[]) {
				byte[] bytes = (byte[]) o;
				if (bytes.length > threshold) {
					// 做很多事情,例如用ELK完成收集和展示
				}
			}
		}
	}
}

5.監(jiān)控報警

bigkey的大操作,通常會引起客戶端輸入或者輸出緩沖區(qū)的異常,Redis提供了info clients里面包含的客戶端輸入緩沖區(qū)的字節(jié)數(shù)以及輸出緩沖區(qū)的隊列長度,可以重點關(guān)注下:

127.0.0.1:6379> info clients
client_longest_output_list:xxxxxc
lient_biggest_input_buf:xxxxx

如果想知道具體的客戶端,可以使用client list命令來查找

redis-cli client list
id=3 addr=127.0.0.1:58500 fd=8 name= age=3978 idle=25 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=26263554 events=r cmd=hgetall

6.改源碼

這個其實也是能做的,但是各方面成本比較高,對于一般公司來說不適用。

我個人的最佳實踐就是:

(1) Redis端與客戶端相結(jié)合:--bigkeys臨時用、scan長期做排除隱患(盡可能本地化)、客戶端實時監(jiān)控。

(2) 監(jiān)控報警要跟上

(3) debug object盡量少用

(4) 所有數(shù)據(jù)平臺化

(5) 要和開發(fā)同學(xué)強調(diào)bigkey的危害

五、如何刪除

如果發(fā)現(xiàn)了bigkey,而且確認(rèn)是垃圾是不是直接del就可以了,來看一組數(shù)據(jù):

阿里云的redis規(guī)范

 

可以看到對于string類型,刪除速度還是可以接受的。但對于二級數(shù)據(jù)結(jié)構(gòu),隨著元素個數(shù)的增長以及每個元素字節(jié)數(shù)的增大,刪除速度會越來越慢,存在阻塞Redis的隱患。所以在刪除它們時候建議采用漸進式的方式來完成:hscan、ltrim、sscan、zscan。

如果你使用Redis 4.0+,一條異步刪除unlink就解決,就可以忽略下面內(nèi)容。

1.字符串:

一般來說,對于string類型使用del命令不會產(chǎn)生阻塞。

del bigkey

2.hash

使用hscan命令,每次獲取部分(例如100個)field-value,在利用hdel刪除每個field(為了快速可以使用pipeline)。public void delBigHash(String bigKey) {
	Jedis jedis = new Jedis("127.0.0.1", 6379);
	// 游標(biāo)
	String cursor = "0";
	while(true) {
		ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(bigKey, cursor, new ScanParams().count(100));
		// 每次掃描后獲取新的游標(biāo)
		cursor = scanResult.getStringCursor();
		// 獲取掃描結(jié)果
		List<Entry<String, String>> list = scanResult.getResult();
		if(list == null || list.size() == 0) {
			continue;
		}
		String[] fields = getFieldsFrom(list);
		// 刪除多個field
		jedis.hdel(bigKey, fields);
		// 游標(biāo)為0時停止
		if(cursor.equals("0")) {
			break;
		}
	}
	// 最終刪除key
	jedis.del(bigKey);
}
/**
 * 獲取field數(shù)組
 */
private String[] getFieldsFrom(List<Entry<String, String>> list) {
	List<String> fields = new ArrayList<String>();
	for (Entry<String, String> entry : list) {
		fields.add(entry.getKey());
	}
	return fields.toArray(new String[fields.size()]);
}

3.list

Redis并沒有提供lscan這樣的API來遍歷列表類型,但是提供了ltrim這樣的命令可以漸進式的刪除列表元素,直到把列表刪除。

public void delBigList(String bigKey) {
	Jedis jedis = new Jedis("127.0.0.1", 6379);
	long llen = jedis.llen(bigKey);
	int counter = 0;
	int left = 100;
	while(counter < llen) {
		// 每次從左側(cè)截掉100個
		jedis.ltrim(bigKey, left, llen);
		counter += left;
	}
	// 最終刪除key
	jedis.del(bigKey);
}

4.set

使用sscan命令,每次獲取部分(例如100個)元素,在利用srem刪除每個元素。

ublic void delBigSet(String bigKey) {
	Jedis jedis = new Jedis("127.0.0.1", 6379);
	// 游標(biāo)
	String cursor = "0";
	while(true) {
		ScanResult<String> scanResult = jedis.sscan(bigKey, cursor, new ScanParams().count(100));
		// 每次掃描后獲取新的游標(biāo)
		cursor = scanResult.getStringCursor();
		// 獲取掃描結(jié)果
		List<String> list = scanResult.getResult();
		if(list == null || list.size() == 0) {
			continue;
		}
		
		jedis.srem(bigKey, list.toArray(new String[list.size()]));
		// 游標(biāo)為0時停止
		if(cursor.equals("0")) {
			break;
		}
	}
	// 最終刪除key
	jedis.del(bigKey);
}

5.sorted set

使用zscan命令,每次獲取部分(例如100個)元素,在利用zremrangebyrank刪除元素。public void delBigSortedSet(String bigKey) {
	long startTime = System.currentTimeMillis();
	Jedis jedis = new Jedis(HOST, PORT);
	// 游標(biāo)
	String cursor = "0";
	while(true) {
		ScanResult<Tuple> scanResult = jedis.zscan(bigKey, cursor, new ScanParams().count(100));
		// 每次掃描后獲取新的游標(biāo)
		cursor = scanResult.getStringCursor();
		// 獲取掃描結(jié)果
		List<Tuple> list = scanResult.getResult();
		if(list == null || list.size() == 0) {
			continue;
		}
		String[] members = getMembers(list);
		jedis.zrem(bigKey, members);
		// 游標(biāo)為0時停止
		if(cursor.equals("0")) {
			break;
		}
	}
	// 最終刪除key
	jedis.del(bigKey);
}
public void delBigSortedSet2(String bigKey) {
	Jedis jedis = new Jedis(HOST, PORT);
	long zcard = jedis.zcard(bigKey);
	int counter = 0;
	int incr = 100;
	while(counter < zcard) {
		jedis.zremrangeByRank(bigKey, 0, 100);
		// 每次從左側(cè)截掉100個
		counter += incr;
	}
	// 最終刪除key
	jedis.del(bigKey);
}

六、如何優(yōu)化

1.拆

big list: list1、list2、...listN

big hash:可以做二次的hash,例如hash%100

日期類:key20190320、key20190321、key_20190322。

2.本地緩存

減少訪問redis次數(shù),降低危害,但是要注意這里有可能因此本地的一些開銷(例如使用堆外內(nèi)存會涉及序列化,bigkey對序列化的開銷也不?。?/p>

7、總結(jié):

由于開發(fā)人員對Redis的理解程度不同,在實際開發(fā)中出現(xiàn)bigkey在所難免,重要的能通過合理的檢測機制及時找到它們,進行處理。作為開發(fā)人員應(yīng)該在業(yè)務(wù)開發(fā)時不能將Redis簡單暴力的使用,應(yīng)該在數(shù)據(jù)結(jié)構(gòu)的選擇和設(shè)計上更加合理,例如出現(xiàn)了bigkey,要思考一下可不可以做一些優(yōu)化(例如二級索引)盡量的讓這些bigkey消失在業(yè)務(wù)中,如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出來(例如有時候僅僅需要hmget,而不是hgetall),刪除也是一樣,盡量使用優(yōu)雅的方式來處理。

分享到:
標(biāo)簽:redis
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定