最简单的配置使用
引入
<dependencies>
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
配置yml
spring:
redis:
host: 192.168.0.101
port: 6379
database: 0
使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisApplication.class)
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test(){
redisTemplate.opsForValue().set("test","hello redis");
}
}
可以看出只需3步就能轻松操作redis
,那么其背后是如何实现的呢?
redis
的自动装配
自动配置初始化流程相关类
SpringBoot
启动类需要声明@SpringBootApplication
注解,@SpringBootApplication
注解里面还有个@EnableAutoConfiguration
注解,该注解表示激活自动配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
EnableAutoConfiguration
有exclude
和excludeName
属性,如果启动的时候不想加载某个自动配置类,可以用该属性,例如@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
EnableAutoConfiguration
还导入了一个AutoConfigurationImportSelector
自动配置导入选择器类,该类最后会调用SpringFactoriesLoader
的loadSpringFactories
方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
...
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
关键来了,该方法加载了一个META-INFO/spring.factories
文件,定位文件位置
其中就有个RedisAutoConfiguration
配置,进入RedisAutoConfiguration
配置类,就可以看到ReidsTemplate
和StringRedisTemplate
总结:SpringBoot
启动的时候会加载autoconfigure
->META-INF
下的spring.factories
文件,RedisAutoConfiguration
就是其中之一,RedisAutoConfiguration
为我们注入了RedisTemplate
和StringRedisTemplate
组件
如何连接到redis
这里分两步,管理和连接
管理:用哪个客户端?用不用连接池?是主从还是哨兵?
连接:拿到对应的客户端,进行连接操作
管理
在RedisAutoConfiguration
类中可以看出该类导入了LettuceConnectionConfiguration
和JedisConnectionConfiguration
两个类,这就是连接Redis
的两个客户端
Jedis
是通过socket
实现的,是传统的BIO
模型。每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,这种模型在实际生产中很少使用
Lettuce
是通过netty
实现的,是NIO
模型。可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程),这样可以大大节省系统资源。
SpringBoot
默认用的是Lettuce
客户端,这里就通过Lettuce
来看是如何连接Redis
Redis配置流程相关类
首先LettuceConnectionConfiguration
会判断是sentinel
还是cluster
还是默认
模式,然后进行初始化LettuceConnectionFactory
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources)
throws UnknownHostException {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources,
this.properties.getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if (getSentinelConfig() != null) {
return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
}
return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
LettuceConnectionFactory
然后会初始化LettuceConnectionProvider
,这个类用来提供连接
private @Nullable LettuceConnectionProvider connectionProvider;
private @Nullable LettuceConnectionProvider reactiveConnectionProvider;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() {
this.client = createClient();
this.connectionProvider = createConnectionProvider(client, LettuceConnection.CODEC);
this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC);
if (isClusterAware()) {
this.clusterCommandExecutor = new ClusterCommandExecutor(
new LettuceClusterTopologyProvider((RedisClusterClient) client),
new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
EXCEPTION_TRANSLATION);
}
}
默认情况LettuceConnectionProvider
的实现类是StandaloneConnectionProvider
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
ReadFrom readFrom = getClientConfiguration().getReadFrom().orElse(null);
if (isStaticMasterReplicaAware()) {
List<RedisURI> nodes = ((RedisStaticMasterReplicaConfiguration) configuration).getNodes().stream() //
.map(it -> createRedisURIAndApplySettings(it.getHostName(), it.getPort())) //
.peek(it -> it.setDatabase(getDatabase())) //
.collect(Collectors.toList());
return new StaticMasterReplicaConnectionProvider((RedisClient) client, codec, nodes, readFrom);
}
if (isClusterAware()) {
return new ClusterConnectionProvider((RedisClusterClient) client, codec, readFrom);
}
return new StandaloneConnectionProvider((RedisClient) client, codec, readFrom);
}
获取回来的LettuceConnectionProvider
会放在LettucePoolingConnectionProvider
中
private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
LettuceConnectionProvider connectionProvider = doCreateConnectionProvider(client, codec);
if (this.clientConfiguration instanceof LettucePoolingClientConfiguration) {
return new LettucePoolingConnectionProvider(connectionProvider,
(LettucePoolingClientConfiguration) this.clientConfiguration);
}
return connectionProvider;
}
连接
当RedisTemplate
执行时,会调用execute
方法,然后获取RedisConnectionFactory
,最后用RedisConnectionUtils
来获取RedisConnection
@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = getRequiredConnectionFactory();
RedisConnection conn = null;
try {
if (enableTransactionSupport) {
// only bind resources in case of potential transaction synchronization
conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
} else {
conn = RedisConnectionUtils.getConnection(factory);
}
....
} finally {
RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
}
}
RedisConnectionUtils
的getConnection
@Override
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
if (connectionType.equals(StatefulRedisSentinelConnection.class)) {
return connectionType.cast(client.connectSentinel());
}
if (connectionType.equals(StatefulRedisPubSubConnection.class)) {
return connectionType.cast(client.connectPubSub(codec));
}
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
return connectionType.cast(readFrom.map(it -> this.masterReplicaConnection(redisURISupplier.get(), it))
.orElseGet(() -> client.connect(codec)));
}
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
}
client.connect
最后的调用
private void initializeChannelAsync0(ConnectionBuilder connectionBuilder, CompletableFuture<Channel> channelReadyFuture,
SocketAddress redisAddress) {
logger.debug("Connecting to Redis at {}", redisAddress);
Bootstrap redisBootstrap = connectionBuilder.bootstrap();
RedisChannelInitializer initializer = connectionBuilder.build();
redisBootstrap.handler(initializer);
clientResources.nettyCustomizer().afterBootstrapInitialized(redisBootstrap);
CompletableFuture<Boolean> initFuture = initializer.channelInitialized();
ChannelFuture connectFuture = redisBootstrap.connect(redisAddress);
}
可以看出最后是通过netty
连接到redis
的
附加jedis
连接redis
的方式
@Override
public Socket createSocket() throws JedisConnectionException {
Socket socket = null;
try {
socket = new Socket();
...
HostAndPort hostAndPort = getSocketHostAndPort();
socket.connect(new InetSocketAddress(hostAndPort.getHost(), hostAndPort.getPort()), getConnectionTimeout());
socket.setSoTimeout(getSoTimeout());
...
return socket;
} catch (IOException ex) {
IOUtils.closeQuietly(socket);
throw new JedisConnectionException("Failed to create socket.", ex);
}
}
lettuce
和redis
功能比较
功能 | lettuce | redis |
---|---|---|
独立连接 | X | X |
主/副本连接 | X | |
Redis前哨 | 主查询,前哨身份验证,副本读取 | 主查询 |
Redis集群 | 群集连接,群集节点连接,副本读取 | 群集连接,群集节点连接 |
运输渠道 | TCP,OS原生TCP(epoll,kqueue),Unix域套接字 | TCP协议 |
连接池 | X(使用commons-pool2) | X(使用commons-pool2) |
其他连接功能 | 非阻塞命令的单例连接共享 | JedisShardInfo支持 |
SSL支持 | X | X |
发布/订阅 | X | X |
流水线 | X | X |
交易次数 | X | X |
数据类型支持 | Key, String, List, Set, Sorted Set, Hash, Server, Stream, Scripting, Geo, HyperLogLog | Key, String, List, Set, Sorted Set, Hash, Server, Scripting, Geo, HyperLogLog |
响应式(非阻塞)API | X |
SpringBoot
关于redis
的相关配置
# REDIS (RedisProperties)
spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster.
spring.redis.cluster.nodes= # Comma-separated list of "host:port" pairs to bootstrap from.
spring.redis.database=0 # Database index used by the connection factory.
spring.redis.url= # Connection URL. Overrides host, port, and password. User is ignored. Example: redis://user:password@example.com:6379
spring.redis.host=localhost # Redis server host.
spring.redis.jedis.pool.max-active=8 # Maximum number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
spring.redis.jedis.pool.max-idle=8 # Maximum number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
spring.redis.jedis.pool.max-wait=-1ms # Maximum amount of time a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely.
spring.redis.jedis.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
spring.redis.lettuce.pool.max-active=8 # Maximum number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
spring.redis.lettuce.pool.max-idle=8 # Maximum number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
spring.redis.lettuce.pool.max-wait=-1ms # Maximum amount of time a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely.
spring.redis.lettuce.pool.min-idle=0 # Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
spring.redis.lettuce.shutdown-timeout=100ms # Shutdown timeout.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
spring.redis.sentinel.master= # Name of the Redis server.
spring.redis.sentinel.nodes= # Comma-separated list of "host:port" pairs.
spring.redis.ssl=false # Whether to enable SSL support.
spring.redis.timeout= # Connection timeout.