Redis缓存--Jedis
一、Jedis实例

二、JedisPool
如果应用非常频繁地创建和销毁 Jedis 实例,虽然节省了系统资源与网络带宽,但会大大降低系统性能。因为创建和销毁 Socket 连接是比较耗时的。此时可以使用 Jedis 连接池来解决该问题。
使用 JedisPool 与使用 Jedis 实例的区别是,JedisPool 是全局性的,整个类只需创建一次即可,然后每次需要操作 Redis 时,只需从 JedisPool 中拿出一个 Jedis 实例直接使用即可。
public class JedisPoolTest {private JedisPool jedisPool = new JedisPool("redis", 6379);// value为String的情况@Testpublic void test01() {try (Jedis jedis = jedisPool.getResource()) {jedis.set("name", "张三");jedis.mset("age", "23", "depart", "行政部");System.out.println("name = " + jedis.get("name"));System.out.println("age = " + jedis.get("age"));System.out.println("depart = " + jedis.get("depart"));}}
}使用完毕后,无需释放 Jedis 实例,只需返回 JedisPool 即可。
三、JedisPooled
对于每次对 Redis 的操作都需要使用 try-with-resource 块是比较麻烦的,而使用JedisPooled 则无需再使用该结构来自动释放资源了。
public class JedisPooledTest {private JedisPooled jedisPooled = new JedisPooled("redis", 6379);// value为String的情况@Testpublic void test01() {jedisPooled.set("name", "张三");jedisPooled.mset("age", "23", "depart", "行政部");System.out.println("name = " + jedisPooled.get("name"));System.out.println("age = " + jedisPooled.get("age"));System.out.println("depart = " + jedisPooled.get("depart"));}
}四、连接Sentinel高可用集群
对于 Sentinel 高可用集群的连接,直接使用 JedisSentinelPool 即可。在该客户端只需注册所有 Sentinel 节点及其监控的 Master 的名称即可,无需出现 master-slave 的任何地址信息。
其采用的也是 JedisPool,使用完毕的 Jedis 也需要通过 close()方法将其返回给连接池。
public class JedisSentinelPoolTest {private JedisSentinelPool jedisPool;{HashSet<String> sentinels = new HashSet<>();sentinels.add("redis:26380");sentinels.add("redis:26381");sentinels.add("redis:26382");jedisPool = new JedisSentinelPool("mymaster", sentinels);}// value为String的情况@Testpublic void test01() {try (Jedis jedis = jedisPool.getResource()) {jedis.set("name", "张三");jedis.mset("age", "23", "depart", "行政部");System.out.println("name = " + jedis.get("name"));System.out.println("age = " + jedis.get("age"));System.out.println("depart = " + jedis.get("depart"));}}
}五、连接分布式系统
对于 Redis 的分布式系统的连接,直接使用 JedisCluster 即可。
其底层采用的也是 Jedis 连接池技术。每次使用完毕后,无需显式关闭,其会自动关闭。
对于 JedisCluster 常用的构造器有两个。一个是只需一个集群节点的构造器,这个节点可以是集群中的任意节点,只要连接上了该节点,就连接上了整个集群。但该构造器存在一个风险:其指定的这个节点在连接之前恰好宕机,那么该客户端将无法连接上集群。所以,推荐使用第二个构造器,即将集群中所有节点全部罗列出来。这样就会避免这种风险了。
public class JedisClusterTest {private JedisCluster jedisCluster;{// 连接Cluster中的任意主节点// HostAndPort node = new HostAndPort("redis", 6380);// jedisCluster = new JedisCluster(node);// 连接整个ClusterHashSet<HostAndPort> nodes = new HashSet<>();nodes.add(new HostAndPort("redis", 6380));nodes.add(new HostAndPort("redis", 6381));nodes.add(new HostAndPort("redis", 6382));nodes.add(new HostAndPort("redis", 6383));nodes.add(new HostAndPort("redis", 6384));nodes.add(new HostAndPort("redis", 6385));jedisCluster = new JedisCluster(nodes);}// value为String的情况@Testpublic void test01() {jedisCluster.set("name", "张三");// 代码中存在注释的多值设置和打印逻辑// jedisCluster.mset("age", "23", "depart", "行政部");System.out.println("name = " + jedisCluster.get("name"));}
}六、操作事务
对于 Redis 事务的操作,Jedis 提供了 multi()、watch()、unwatch()方法来对应 Redis 中的 multi、watch、unwatch 命令。Jedis 的 multi()方法返回一个 Transaction 对象,其 exec()与 discard() 方法用于执行和取消事务的执行。
6.1 抛出Java异常
public class JedisTxTest {private JedisPool jedisPool = new JedisPool("redis", 6379);@Testpublic void test01() {try (Jedis jedis = jedisPool.getResource()) {jedis.set("name", "张三");jedis.set("age", "23");Transaction multi = jedis.multi();try {multi.set("name", "李四");// 构造一个Java异常int i = 3 / 0;multi.set("age", "24");multi.exec();} catch (Exception e) {// 一旦发生异常,全部回滚multi.discard();} finally {System.out.println(jedis.get("name")); // 张三System.out.println(jedis.get("age")); // 23}}}
}
6.2 Redis异常
public class JedisTxTest {private JedisPool jedisPool = new JedisPool("redis", 6379);@Testvoid test02() {try (Jedis jedis = jedisPool.getResource()) {jedis.set("name", "张三");jedis.set("age", "23");Transaction multi = jedis.multi();try {multi.set("name", "李四");// 构造一个Redis异常,不会影响后面的执行multi.incr("name");multi.set("age", "24");multi.exec();} catch (Exception e) {// Redis异常不会被Java代码捕获到multi.discard();} finally {System.out.println(jedis.get("name")); // 李四System.out.println(jedis.get("age")); // 24}}}
}6.3 watch
multi:开启事务,之后的所有命令会被放入事务队列中,不会立即执行,直到调用 exec 才会批量执行。
watch key:监视指定的键,若在事务执行前这些键被其他客户端修改,则事务会被打断(不执行),用于实现乐观锁。
unwatch:取消对所有键的监视,通常在事务执行前或放弃事务时使用。
@Test
void test03() {try (Jedis jedis = jedisPool.getResource()) {jedis.set("age", "23");System.out.println("增一前的age值:" + jedis.get("age")); // 23jedis.watch("age");Transaction multi = jedis.multi();multi.incr("age");multi.exec();System.out.println("增一后的age值:" + jedis.get("age"));}
}七、SpringBoot整合Redis
@CacheEvict 用于实现 value 指定缓存空间中缓存数据的清空。
allEntries 为 true 指定清空该缓存空间所有数据。如果不想清空所有,则需通过 key 属性指定要清理的 key 数据。
@Service
public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductDao dao;@Autowiredprivate RedisTemplate<Object, Object> rt;@Override@Transactional(rollbackFor = Exception.class)@CacheEvict(value = "pc", allEntries = true)public void saveProduct(Product product) {dao.insertProduct(product);}
}
@Override
@Cacheable(value = "pc", key = "'product_' + #name")
public List<Product> findProductsByName(String name) {return dao.selectProductsByName(name);
}@Override
@Cacheable(value = "pc", key = "'product_all'")
public List<Product> findAllProducts() {return dao.selectAllProducts();
}// @Cacheable 用于指定将查询结果使用指定的 key 缓存到指定缓存空间
// 如果再有对该查询数据的访问,则会先从缓存中查看。
@Override
public Double findTurnover() {// 获取Redis中指定key("turnover")的操作对象,用于后续对该key的读写操作// rt 是代码中注入的 RedisTemplate<Object, Object> 对象// 它是 Spring 封装的 Redis 操作模板,提供了对 Redis 各种数据类型(String、Hash、List 等)的通用操作方法// boundValueOps("turnover"):绑定指定 key 并返回操作对象// bound 意为 “绑定”,表示后续的所有操作都会针对指定的 key(这里是 turnover),无需每次操作都显式传入 key// // ValueOps 表示该操作对象专门用于处理 Redis 中的 String 类型数据(即 key-value 都是字符串或可序列化对象的键值对)// BoundValueOperations:绑定 key 后的操作工具类BoundValueOperations<Object, Object> ops = rt.boundValueOps("turnover");// 从Redis缓存中获取key为"turnover"的值Object turnover = ops.get();// 判断缓存中是否存在该数据(若不存在则为null)if (turnover == null) {// 创建当前日期对象Date date = new Date();// 创建日期格式化对象,指定格式为"yyyy-MM-dd"SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 从数据库中查询当天(格式化后的日期)的营业额数据turnover = dao.selectTurnover(sdf.format(date));}// 将营业额数据存入Redis缓存,并设置过期时间为10秒(10秒后自动失效)ops.set(turnover, 10, TimeUnit.SECONDS);// 将获取到的营业额数据转换为Double类型并返回return (Double) turnover;
}