keys *
這個命令千萬別在生產環境亂用。特別是數據龐大的狀況下。由於Keys會引起Redis鎖,而且增長Redis的CPU佔用。不少公司的運維都是禁止了這個命令的html
當須要掃描key,匹配出本身須要的key時,可使用 scan
命令java
scan
操做的Helper實現import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisHelper { @Autowired private StringRedisTemplate stringRedisTemplate; /** * scan 實現 * @param pattern 表達式 * @param consumer 對迭代到的key進行操做 */ public void scan(String pattern, Consumer<byte[]> consumer) { this.stringRedisTemplate.execute((RedisConnection connection) -> { try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) { cursor.forEachRemaining(consumer); return null; } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } }); } /** * 獲取符合條件的key * @param pattern 表達式 * @return */ public List<String> keys(String pattern) { List<String> keys = new ArrayList<>(); this.scan(pattern, item -> { //符合條件的key String key = new String(item,StandardCharsets.UTF_8); keys.add(key); }); return keys; } }
可是會有一個問題:無法移動cursor,也只能scan一次,而且容易致使redis連接報錯redis
http://doc.redisfans.com/key/scan.htmlspring
.count(Integer.MAX_VALUE)
,一會兒全查回來;可是這樣子和 keys 有啥區別呢?搞笑臉 & 疑問臉(JedisCommands) connection.getNativeConnection()
的 hscan、sscan、zscan 方法實現cursor遍歷,參照下文2.2章節try { Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan("field", ScanOptions.scanOptions().match("*").count(1000).build()); while (cursor.hasNext()) { Object key = cursor.next().getKey(); Object valueSet = cursor.next().getValue(); } //關閉cursor cursor.close(); } catch (IOException e) { e.printStackTrace(); }
client lists
`info clients`info stats
命令查看客戶端鏈接狀態,會發現scan操做一直存在client list ...... id=1531156 addr=xxx:55845 fd=8 name= age=80 idle=11 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=scan ...... org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback<T>, boolean, boolean) finally { RedisConnectionUtils.releaseConnection(conn, factory); }
public Set<String> scan(String matchKey) { Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<String> keysTmp = new HashSet<>(); Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build()); while (cursor.hasNext()) { keysTmp.add(new String(cursor.next())); } return keysTmp; }); return keys; }
connection.getNativeConnection
;connection.getNativeConnection()
實際對象是Jedis(debug能夠看出) ,Jedis實現了不少接口public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands, AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands
public Set<String> scan(String key) { return redisTemplate.execute((RedisCallback<Set<String>>) connection -> { Set<String> keys = Sets.newHashSet(); JedisCommands commands = (JedisCommands) connection.getNativeConnection(); MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands; ScanParams scanParams = new ScanParams(); scanParams.match("*" + key + "*"); scanParams.count(1000); ScanResult<String> scan = multiKeyCommands.scan("0", scanParams); while (null != scan.getStringCursor()) { keys.addAll(scan.getResult()); if (!StringUtils.equals("0", scan.getStringCursor())) { scan = multiKeyCommands.scan(scan.getStringCursor(), scanParams); continue; } else { break; } } return keys; }); }
client lists
`info clients`info stats
查看config get maxclients
查詢redis容許的最大鏈接數 是 100001) "maxclients" 2) "10000"`
redis-cli
在其餘機器上也能夠直接登陸 操做綜上,redis自己沒有卡死數據庫
netstat
查看和redis的鏈接,6333是redis端口;鏈接一直存在➜ ~ netstat -an | grep 6333 netstat -an | grep 6333 tcp4 0 0 xx.xx.xx.aa.52981 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52979 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52976 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52971 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52969 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52967 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52964 xx.xx.xx.bb.6333 ESTABLISHED tcp4 0 0 xx.xx.xx.aa.52961 xx.xx.xx.bb.6333 ESTABLISHED
jstack
查看應用的堆棧信息"http-nio-7007-exec-2" #139 daemon prio=5 os_prio=31 tid=0x00007fda36c1c000 nid=0xdd03 waiting on condition [0x00007000171ff000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000006c26ef560> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:590) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:441) at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:362) at redis.clients.util.Pool.getResource(Pool.java:49) at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226) at redis.clients.jedis.JedisPool.getResource(JedisPool.java:16) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:276) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:469) at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132) at org.springframework.data.redis.core.RedisTemplate.executeWithStickyConnection(RedisTemplate.java:371) at org.springframework.data.redis.core.DefaultHashOperations.scan(DefaultHashOperations.java:244)
綜上,是應用側卡死apache
client lists
顯示 scan 鏈接還在,沒有釋放;應用線程也仍是處於卡死狀態config get timeout
,redis未設置超時時間,能夠用 config set timeout xxx
設置,單位秒;可是設置了redis的超時,redis釋放了鏈接,應用仍是同樣卡住1) "timeout" 2) "0"
netstat
查看和redis的鏈接,6333是redis端口;鏈接從ESTABLISHED變成了CLOSE_WAIT;jstack
和 原來表現同樣,卡在JedisConnectionFactory.getConnection
➜ ~ netstat -an | grep 6333 netstat -an | grep 6333 tcp4 0 0 xx.xx.xx.aa.52981 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52979 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52976 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52971 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52969 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52967 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52964 xx.xx.xx.bb.6333 CLOSE_WAIT tcp4 0 0 xx.xx.xx.aa.52961 xx.xx.xx.bb.6333 CLOSE_WAIT
netstat -an
基本能夠肯定 redis 鏈接池的大小是 8 ;結合代碼配置,沒有指定的話,默認也確實是8redis.clients.jedis.JedisPoolConfig private int maxTotal = 8; private int maxIdle = 8; private int minIdle = 0;
@Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(redisHost); redisStandaloneConfiguration.setPort(redisPort); redisStandaloneConfiguration.setPassword(RedisPassword.of(redisPasswd)); JedisConnectionFactory cf = new JedisConnectionFactory(redisStandaloneConfiguration); cf.afterPropertiesSet(); return cf; }
readTimeout,connectTimeout不指定,有默認值 2000 ms安全
org.springframework.data.redis.connection.jedis.JedisConnectionFactory.MutableJedisClientConfiguration private Duration readTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT); private Duration connectTimeout = Duration.ofMillis(Protocol.DEFAULT_TIMEOUT);
B. 修改後配置bash
@Bean public RedisConnectionFactory redisConnectionFactory() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(16); // --最多能夠創建16個鏈接了 jedisPoolConfig.setMaxWaitMillis(10000); // --10s獲取不到鏈接池的鏈接, // --直接報錯Could not get a resource from the pool jedisPoolConfig.setMaxIdle(16); jedisPoolConfig.setMinIdle(0); JedisConnectionFactory cf = new JedisConnectionFactory(jedisPoolConfig); cf.setHostName(redisHost); // -- @Deprecated cf.setPort(redisPort); // -- @Deprecated cf.setPassword(redisPasswd); // -- @Deprecated cf.setTimeout(30000); // -- @Deprecated 貌似沒生效,30s超時,沒有關閉鏈接池的鏈接; // --redis沒有設置超時,會一直ESTABLISHED;redis設置了超時,且超時以後,會一直CLOSE_WAIT cf.afterPropertiesSet(); return cf; }
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(redisHost); redisStandaloneConfiguration.setPort(redisPort); redisStandaloneConfiguration.setPassword(RedisPassword.of(redisPasswd)); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(16); jedisPoolConfig.setMaxWaitMillis(10000); jedisPoolConfig.setMaxIdle(16); jedisPoolConfig.setMinIdle(0); cf = new JedisConnectionFactory(redisStandaloneConfiguration, JedisClientConfiguration.builder() .readTimeout(Duration.ofSeconds(30)) .connectTimeout(Duration.ofSeconds(30)) .usePooling().poolConfig(jedisPoolConfig).build());
redistemplate-遊標scan使用注意事項如何使用RedisTemplate訪問Redis數據結構數據結構