前言
之前在做 秒殺架構實踐 時有提到對 distributed-redis-tool 的一次小升級,但是沒有細說。
其實主要原因是:
秒殺時我做壓測:由于集成了這個限流組件,并發又比較大,所以導致連接、斷開 Redis 非常頻繁。
最終導致獲取不了 Redis connection 的異常。
池化技術
這就是一個典型的對稀缺資源使用不善導致的。
何為稀缺資源?常見的有:
- 線程
- 數據庫連接
- 網絡連接等
這些資源都有共同的特點:創建銷毀成本較高。
這里涉及到的 Redis 連接也屬于該類資源。
我們希望將這些稀有資源管理起來放到一個池子里,當需要時就從中獲取,用完就放回去,不夠用時就等待(或返回)。
這樣我們只需要初始化并維護好這個池子,就能避免頻繁的創建、銷毀這些資源(也有資源長期未使用需要縮容的情況)。
通常我們稱這項姿勢為池化技術,如常見的:
- 線程池
- 各種資源的連接池等。
為此我將使用到 Redis 的 分布式鎖、分布式限流 都升級為利用連接池來獲取 Redis 的連接。
這里以分布式鎖為例:
將使用的 api 修改為:
原有:
@Configuration
public class RedisLockConfig {@Beanpublic RedisLock build(){//Need to get Redis connection RedisLock redisLock = new RedisLock() ;HostAndPort hostAndPort = new HostAndPort("127.0.0.1",7000) ;JedisCluster jedisCluster = new JedisCluster(hostAndPort) ;RedisLock redisLock = new RedisLock.Builder(jedisCluster).lockPrefix("lock_test").sleepTime(100).build();return redisLock ;}}
現在:
@Configuration
public class RedisLockConfig {private Logger logger = LoggerFactory.getLogger(RedisLockConfig.class);@Autowiredprivate JedisConnectionFactory jedisConnectionFactory;@Beanpublic RedisLock build() {RedisLock redisLock = new RedisLock.Builder(jedisConnectionFactory,RedisToolsConstant.SINGLE).lockPrefix("lock_").sleepTime(100).build();return redisLock;}
}
將以前的 Jedis 修改為 JedisConnectionFactory
,后續的 Redis 連接就可通過這個對象獲取。
并且顯示的傳入使用 RedisCluster 還是單機的 Redis。
所以在真正操作 Redis 時需要修改:
public boolean tryLock(String key, String request) {//get connectionObject connection = getConnection();String result ;if (connection instanceof Jedis){result = ((Jedis) connection).set(lockPrefix + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);((Jedis) connection).close();}else {result = ((JedisCluster) connection).set(lockPrefix + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);try {((JedisCluster) connection).close();} catch (IOException e) {logger.error("IOException",e);}}if (LOCK_MSG.equals(result)) {return true;} else {return false;}}//獲取連接private Object getConnection() {Object connection ;if (type == RedisToolsConstant.SINGLE){RedisConnection redisConnection = jedisConnectionFactory.getConnection();connection = redisConnection.getNativeConnection();}else {RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection();connection = clusterConnection.getNativeConnection() ;}return connection;}
最大的改變就是將原有操作 Redis 的對象(T extends JedisCommands
)改為從連接池中獲取。
由于使用了 org.springframework.data.redis.connection.jedis.JedisConnectionFactory
作為 Redis 連接池。
所以需要再使用時構件好這個對象:
JedisPoolConfig config = new JedisPoolConfig();config.setMaxIdle(10);config.setMaxTotal(300);config.setMaxWaitMillis(10000);config.setTestOnBorrow(true);config.setTestOnReturn(true);RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();redisClusterConfiguration.addClusterNode(new RedisNode("10.19.13.51", 7000));//單機JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(config);//集群//JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisClusterConfiguration) ;jedisConnectionFactory.setHostName("47.98.194.60");jedisConnectionFactory.setPort(6379);jedisConnectionFactory.setPassword("");jedisConnectionFactory.setTimeout(100000);jedisConnectionFactory.afterPropertiesSet();//jedisConnectionFactory.setShardInfo(new JedisShardInfo("47.98.194.60", 6379));//JedisCluster jedisCluster = new JedisCluster(hostAndPort);HostAndPort hostAndPort = new HostAndPort("10.19.13.51", 7000);JedisCluster jedisCluster = new JedisCluster(hostAndPort);redisLock = new RedisLock.Builder(jedisConnectionFactory, RedisToolsConstant.SINGLE).lockPrefix("lock_").sleepTime(100).build();
看起比較麻煩,需要構建對象的較多。
但整合 Spring 使用時就要清晰許多。
配合 Spring
Spring 很大的一個作用就是幫我們管理對象,所以像上文那些看似很復雜的對象都可以交由它來管理:
<!-- jedis 配置 --><bean id="JedispoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxIdle" value="${redis.maxIdle}"/><property name="maxTotal" value="${redis.maxTotal}"/><property name="maxWaitMillis" value="${redis.maxWait}"/><property name="testOnBorrow" value="${redis.testOnBorrow}"/><property name="testOnReturn" value="${redis.testOnBorrow}"/></bean><!-- redis服務器中心 --><bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="poolConfig" ref="JedispoolConfig"/><property name="port" value="${redis.port}"/><property name="hostName" value="${redis.host}"/><property name="password" value="${redis.password}"/><property name="timeout" value="${redis.timeout}"></property></bean><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="connectionFactory"/><property name="keySerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property><property name="valueSerializer"><bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/></property></bean>
這個其實沒多少好說的,就算是換成 SpringBoot 也是創建 JedispoolConfig,connectionFactory,redisTemplate
這些 bean 即可。
總結
換為連接池之后再進行壓測自然沒有出現獲取不了 Redis 連接的異常(并發達到一定的量也會出錯)說明更新是很有必要的。
推薦有用到該組件的朋友都升級下,也歡迎提出 Issues 和 PR。
項目地址:
https://github.com/crossoverJie/distributed-redis-tool