Spring Data Redis

  • Spring Data: Spring Data的任务是为数据访问提供熟悉且一致的基于Spring的编程模型,同时仍保留底层数据存储的特​​殊特性。
  • Spring Data Redis: 是Spring Data家族的一部分,可以从Spring应用程序轻松配置和访问Redis。 它提供了与store交互的低级和高级抽象,把用户从基础设施问题(基础设施问题)中解救出来。

Spring对Redis的支持

  • Spring Data Redis
    • 支持的客户端Jedis / Lettuce / Redisson等
    • RedisTemplate
    • Repository支持

Jedis支持redis的哪些特性:

  • 排序 Sorting
  • 连接 Connection handling
  • Commands operating on any kind of values
  • Commands operating on string values
  • Commands operating on hashes
  • Commands operating on lists
  • Commands operating on sets
  • Commands operating on sorted sets
  • 事务 Transactions
  • Pipelining
  • 发布/订阅 Publish/Subscribe
  • 持久性控制命令 Persistence control commands
  • Remote server control commands
  • Connection pooling
  • Sharding (MD5, MurmurHash)
  • Key-tags for sharding
  • Sharding with pipelining
  • Scripting with pipelining
  • Redis Cluster

Jedis的使用

  • Jedis不是线程安全的
  • 通过JedisPool获取Jedis实例
  • 直接使用Jedis中的方法

简单示例

最简单的一个示例:

/* 添加依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
*/
@SpringBootApplication
@Slf4j
public class JedisDemoApplication implements ApplicationRunner {

	public static void main(String[] args) {
		SpringApplication.run(JedisDemoApplication.class, args);
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		Jedis jedis = new Jedis("localhost");
		jedis.set("foo","bar");
		String value = jedis.get("foo");
		log.info(value);
	}
}

多线程环境

为什么使用JedisPool:

  • Jedis不是线程安全的
  • 创建大量的Jedis它意味着使用更多的套接字和连接,这也会导致奇怪的错误

下面初始化一个池子,您可以静态地(statically)将池存储在某个地方,它是线程安全的。

JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");

JedisPoolConfig包含许多有用的特定于Redis的连接池默认值。 JedisPool基于Commons Pool 2,因此您可能需要查看Commons Pool的配置。点击此处查看更多。

Jedis实现了Closeable。因此,jedis实例将在执行完最后一个语句后自动关闭:

/// Jedis实现了Closeable。因此,jedis实例将在最后一个语句后自动关闭。
try (Jedis jedis = pool.getResource()) {
  /// ... do stuff here ... for example
  jedis.set("foo", "bar");
  String foobar = jedis.get("foo");
  jedis.zadd("sose", 0, "car"); jedis.zadd("sose", 0, "bike"); 
  Set<String> sose = jedis.zrange("sose", 0, -1);
}
/// ... when closing your application:
pool.close();

或者也可以手动关闭Jedis:

Jedis jedis = null;
try {
  jedis = pool.getResource();
  /// ... do stuff here ... for example
  jedis.set("foo", "bar");
  String foobar = jedis.get("foo");
  jedis.zadd("sose", 0, "car"); jedis.zadd("sose", 0, "bike"); 
  Set<String> sose = jedis.zrange("sose", 0, -1);
} finally {
  // You have to close jedis object. If you don't close then
  // it doesn't release back to pool and you can't get a new
  // resource from pool.
  if (jedis != null) {
    jedis.close();
  }
}
/// ... when closing your application:
pool.close();

或者直接使用@Bean,Autowired方式

//配置文件
redis.host=localhost
redis.maxTotal=5
redis.maxIdle=5
redis.testOnBorrow=true

//java类
@SpringBootApplication
@Slf4j
public class JedisDemoApplication implements ApplicationRunner {

	@Autowired
	private JedisPool jedisPool;
	@Autowired
	private JedisPoolConfig jedisPoolConfig;

	public static void main(String[] args) {
		SpringApplication.run(JedisDemoApplication.class, args);
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		userPool();
	}

	@Bean
	@ConfigurationProperties("redis") //将redis开头的相关属性赋给它
	public JedisPoolConfig jedisPoolConfig() {
		return new JedisPoolConfig();
	}

	@Bean(destroyMethod = "close") //关闭bean的时候
	public JedisPool jedisPool(@Value("${redis.host}") String host) {
		return new JedisPool(jedisPoolConfig(), host);
	}

	private void userPool(){

		log.info(jedisPool.toString());

		try (Jedis jedis = jedisPool.getResource()) {
			jedis.set("foo", "bar");
			String foobar = jedis.get("foo");
			jedis.zadd("sose", 0, "car");
			jedis.zadd("sose", 0, "bike");
			Set<String> sose = jedis.zrange("sose", 0, -1);
			log.info(sose.toString());
		}
		jedisPool.close();
	}

}

主从复制

Setting up master/slave distribution

enable replication 启用复制

Redis主要用于主/从(master/slave)分发。
我们的直接请求地址必须指定主节点(redis服务器)。主节点会复制变更到’从服务器(也是redis服务器)‘。 然后,读取请求可以(但不一定必须)发送到从站,这可以减轻主站的负担。

为了启用复制,有两种方法可以告诉从属服务器它将是给定主服务器的“slaveOf”:

  • Redis Config中指定
  • 在redis实例中指定 IP (或 “localhost”) 和端口
jedis.slaveof("localhost", 6379);  //  if the master is on the same PC which runs your code
jedis.slaveof("192.168.1.35", 6379); 

Note: since Redis 2.6 slaves are read only by default, so write requests to them will result in an error.
If you change that setting they will behave like normal redis servers and accept write requests without errors, but the changes won’t be replicated, and hence those changes are at risk to be silently overwritten, if you mix up your jedis instances.

disable replication / upon failing master, promote a slave

如果主节点挂了,你可能想设置从节点为新的节点。
您应首先(尝试)首先禁用脱机主机的复制,然后,如果您有多个从站,则启用其余从站的复制到新主站:

slave1jedis.slaveofNoOne();
slave2jedis.slaveof("192.168.1.36", 6379); 

Jedis高级部分用法

Transactions

要在Jedis中执行事务,您必须将操作包装在事务块(transaction block)中,与pipelining非常相似:

jedis.watch (key1, key2, ...);
Transaction t = jedis.multi();
t.set("foo", "bar");
t.exec();

需要返回值的情况

Transaction t = jedis.multi();
t.set("fool", "bar"); 
Response<String> result1 = t.get("fool");

t.zadd("foo", 1, "barowitch"); t.zadd("foo", 0, "barinsky"); t.zadd("foo", 0, "barikoviev");
Response<Set<String>> sose = t.zrange("foo", 0, -1);   // get the entire sortedset
t.exec();                                              // dont forget it

String foolbar = result1.get();                       // use Response.get() to retrieve things from a Response
int soseSize = sose.get().size();                      // on sose.get() you can directly call Set methods!

// List<Object> allResults = t.exec();                 // you could still get all results at once, as before

exec()的时候操作才会执行。

Pipelining

相关概念的理解:管道(Pipelining) 一篇不错的文章
简单理解就是命令的批处理。

Pipeline p = jedis.pipelined();
p.set("fool", "bar"); 
p.zadd("foo", 1, "barowitch");  
p.zadd("foo", 0, "barinsky"); 
p.zadd("foo", 0, "barikoviev");
Response<String> pipeString = p.get("fool");
Response<Set<String>> sose = p.zrange("foo", 0, -1);
p.sync(); 

int soseSize = sose.get().size();
Set<String> setBack = sose.get();

订阅/发布

要在Redis中订阅频道,请创建JedisPubSub的实例并在Jedis实例上调用subscribe:

class MyListener extends JedisPubSub {
        public void onMessage(String channel, String message) {
        }

        public void onSubscribe(String channel, int subscribedChannels) {
        }

        public void onUnsubscribe(String channel, int subscribedChannels) {
        }

        public void onPSubscribe(String pattern, int subscribedChannels) {
        }

        public void onPUnsubscribe(String pattern, int subscribedChannels) {
        }
        public void onPMessage(String pattern, String channel, String message) {
        }
}
MyListener l = new MyListener();
jedis.subscribe(l, "foo");

请注意,subscribe是一个阻塞操作,因为它将轮询Redis以查询调用subscribe的线程的响应。 单个JedisPubSub实例可用于订阅多个通道。 您可以在现有的JedisPubSub实例上调用subscribe或psubscribe来更改您的订阅。

Jedis Cluster

有个缺点,分片情况下jedis只支持读master

与spring相结合

后续单独讲

参考

https://github.com/xetorthio/jedis/wiki/Getting-started

https://lanjingling.github.io/categories/redis/