一文涵盖所有工作中遇到的redis操作,让你从此学会redis
本文会从基础篇到进阶篇,逐步来讲解redis和springboot的整合,如何去操作,以及他的作用。让你学会使用redis,爱上使用redis。
介绍redis
首先我们来介绍一下redis
NoSQL 数据库
redis是一个
key - value 存储系统(区别于 MySQL,他存储的是键值对)
之后我们来看
redis的数据结构
String 字符串类型: name: “xiaou”
List 列表:names: [“xiaou”, “dogxiaou”, “xiaou”]
Set 集合:names: [“xiaou”, “dogxiaou”](值不能重复)
Hash 哈希:nameAge: { “xiaou”: 1, “dogxiaou”: 2 }
Zset 集合:names: { xiaou - 9, dogxiaou - 12 }(适合做排行榜)
bloomfilter(布隆过滤器,主要从大量的数据中快速过滤值,比如邮件黑名单拦截)
geo(计算地理位置)
hyperloglog(pv / uv)
pub / sub(发布订阅,类似消息队列)
BitMap (1001010101010101010101010101)
如何与java整合?
Spring Data Redis(推荐)
Spring Data:通用的数据访问框架,定义了一组 增删改查 的接口
mysql、redis、jpa
spring-data-redis
Jedis
独立于 Spring 操作 Redis 的 Java 客户端
要配合 Jedis Pool 使用
Lettuce
高阶 的操作 Redis 的 Java 客户端
异步、连接池
Redisson
分布式操作 Redis 的 Java 客户端,让你像在使用本地的集合一样操作 Redis(分布式 Redis 数据网格)
JetCache
对比
- 如果你用的是 Spring,并且没有过多的定制化要求,可以用 Spring Data Redis,最方便
- 如果你用的不是 SPring,并且追求简单,并且没有过高的性能要求,可以用 Jedis + Jedis Pool
- 如果你的项目不是 Spring,并且追求高性能、高定制化,可以用 Lettuce,支持异步、连接池
- 如果你的项目是分布式的,需要用到一些分布式的特性(比如分布式锁、分布式集合),推荐用 redisson
之后我们来介绍一下redis的基础操作
redis基础操作
这里我们选用的是spring-boot-starter-data-redis
首先我们在本机上开启redis双击运行server这个就可以
看到这个就代表着redis已经成功运行了
之后来看maven的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><!-- @author <a href="https://github.com/liyupi">程序员鱼皮</a> --><!-- @from <a href="https://yupi.icu">编程导航知识星球</a> --><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.xiaou</groupId><artifactId>xiaou-backend</artifactId><version>0.0.1-SNAPSHOT</version><name>xiaou-backend</name><description>xiaou-backend</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>2.2.2</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-boot-starter</artifactId>-->
<!-- <version>3.5.1</version>-->
<!-- </dependency>--><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.4</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.9</version></dependency><!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- swagger--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.4.0</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.6.4</version></dependency><!-- <!– https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis –>--><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId><version>2.6.3</version></dependency><!-- <dependency>-->
<!-- <groupId>org.redisson</groupId>-->
<!-- <artifactId>redisson</artifactId>-->
<!-- <version>3.27.1</version>-->
<!-- </dependency>--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/junit/junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
之后我们对yml进行一个配置
server:port: 8081
#spring:
# datasource:
# driver-class-name: com.mysql.jdbc.Driver
# url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&&allowPublicKeyRetrieval=true #url
# username: root
# password: 1234
spring:redis:database: 4host: localhostport: 6379
之后我们要做的是对redis编写一个配置类
@Configuration
@EnableCaching // 启用缓存功能
public class RedisConfig {// 定义 RedisTemplate Bean,用于操作 Redis 数据@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);// 设置 RedisTemplate 的键和值的序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer()); // 设置键的序列化器为 StringRedisSerializerredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 设置值的序列化器为 GenericJackson2JsonRedisSerializer// 设置 RedisTemplate 的哈希键和哈希值的序列化方式redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // 设置哈希键的序列化器为 StringRedisSerializerredisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)); // 设置哈希值的序列化器为 Jackson2JsonRedisSerializerreturn redisTemplate;}// 定义 RedisCacheManager Bean,用于管理缓存@Beanpublic RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {// 创建 RedisCacheWriter 实例,用于向 Redis 写入缓存,非锁定方式RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());// 创建 RedisCacheConfiguration 实例,配置缓存的默认行为RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())); // 设置缓存值的序列化方式// 返回 RedisCacheManager 实例,用给定的 RedisCacheWriter 和 RedisCacheConfiguration 初始化return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);}
}
之后我们就可以使用redisTemplate进行一个操作
使用RedisTemplate进行常见的Redis操作,如存储、检索和删除数据
首先要做的第一步就是引入RedisTemplate
这里推荐一个redis的可视化工具 quickredis
QuickOfficial - QuickRedis (quick123.net)
@Autowired
private RedisTemplate redisTemplate;
String
首先来看第一个
// 测试设置字符串类型数据到 Redis
@Test
public void testString() {String key = "user:token:0001";redisTemplate.opsForValue().set(key, UUID.randomUUID().toString(), 30, TimeUnit.MINUTES);System.out.println(redisTemplate.opsForValue().get(key));
}
这个就是像redis添加了一个key
之后来看这个
// 测试字符串类型数据自增操作
@Test
public void testString2() {String key = "article:A00001:viewsCount";redisTemplate.opsForValue().increment(key);System.out.println(redisTemplate.opsForValue().get(key));
}
increment实现了对这个key的一个计数功能。比如我们第一次执行,就是1 第二次就是2 以此类推
第三个是测试设置Map类型到redis中
// 测试设置 Map 类型数据到 Redis
@Test
public void testString3() {Map<String, Object> user = new HashMap<>();user.put("id", "0001");user.put("name", "张三疯");user.put("age", 28);user.put("birthday", new Date(2005 - 1900, 10, 03));String key = "user:0001";redisTemplate.opsForValue().set(key, user);System.out.println(redisTemplate.opsForValue().get(key));
}
Hash
// 测试设置哈希类型数据到 Redis@Testpublic void testHash() {String key = "user:0001:cart";Map<String, Object> shoppingCart = new HashMap<>();shoppingCart.put("cartId", "123456789");shoppingCart.put("userId", "987654321");List<Map<String, Object>> items = List.of(Map.of("itemId", "1", "itemName", "手机", "price", 999.99, "quantity", 1),Map.of("itemId", "2", "itemName", "笔记本电脑", "price", 1499.99, "quantity",2),Map.of("itemId", "3", "itemName", "耳机", "price", 49.99, "quantity", 3));shoppingCart.put("items", items);shoppingCart.put("totalAmount", 3149.92);shoppingCart.put("creationTime", "2046-03-07T10:00:00");shoppingCart.put("lastUpdateTime", "2046-03-07T12:30:00");shoppingCart.put("status", "未结账");redisTemplate.opsForHash().putAll(key, shoppingCart);System.out.println(redisTemplate.opsForHash().get(key, "items"));}
set
// 测试设置集合类型数据到 Redis
@Test
public void testSet() {String key = "author:0001:fans";redisTemplate.opsForSet().add(key, "张三", "李四", "王五");System.out.println("粉丝量:" + redisTemplate.opsForSet().size(key));
}
zset
// 测试设置有序集合类型数据到 Redis
@Test
public void testZset() {String key = "user:0001:friends";redisTemplate.opsForZSet().add(key, "张三", System.currentTimeMillis());redisTemplate.opsForZSet().add(key,"李四", System.currentTimeMillis());redisTemplate.opsForZSet().add(key,"王五", System.currentTimeMillis());Set set = redisTemplate.opsForZSet().reverseRange(key, 0, -1);System.out.println(set);
}
list
@Test
public void testList() {String key = "order:queue";Map<String, Object> order1 = new HashMap<>();order1.put("orderId", "1001");order1.put("userId", "2001");order1.put("status", "已完成");order1.put("amount", 500.75);order1.put("creationTime", "2024-03-07T09:30:00");order1.put("lastUpdateTime", "2024-03-07T10:45:00");order1.put("paymentMethod", "在线支付");order1.put("shippingMethod", "自提");order1.put("remarks", "尽快处理");Map<String, Object> order2 = new HashMap<>();order2.put("orderId", "1002");order2.put("userId", "2002");order2.put("status", "待处理");order2.put("amount", 280.99);order2.put("creationTime", "2024-03-07T11:00:00");order2.put("lastUpdateTime", "2024-03-07T11:00:00");order2.put("paymentMethod", "货到付款");order2.put("shippingMethod", "快递配送");order2.put("remarks", "注意保鲜");// A程序接收订单请求并将其加入队列redisTemplate.opsForList().leftPush(key,order1);redisTemplate.opsForList().leftPush(key,order2);// B程序从订单队列中获取订单数据并处理System.out.println("处理订单:" + redisTemplate.opsForList().rightPop(key));
}
这是一些简单的操作,除了这些,我们来介绍一些别的东西
redis进阶操作
@RedisHash注解
用于将Java对象映射到Redis的Hash数据结构中,使得对象的存储和检索变得更加简单
首先我们创建和实体类一样的数据库表
这个是集体类
@Data
@RedisHash
public class User {@Idprivate Integer id;private String name;private Integer age;private String phone;
}
2.创建接口,注意需要继承CrudRepository,这样改接口就具备对应实体的redis中的增删改查操作
public interface UserRedisMapper extends CrudRepository<User,Integer> {
}
之后进行一个测试
// 测试 Redis 与 Spring Data Redis 集成的操作
@Test
public void testRedisHash(){User user = new User();user.setId(100);user.setName("张三疯");user.setAge(18);user.setPhone("19988889999");// 保存userRedisMapper.save(user);// 读取User redisUser = userRedisMapper.findById(100).get();System.out.println("redisUser:" + redisUser);// 更新user.setPhone("18899998888");userRedisMapper.save(user);// 删除//userRedisMapper.deleteById(100);// 判断存在boolean exists = userRedisMapper.existsById(100);System.out.println("exists: " + exists);
}
这样就可以用redis来对数据库进行一个操作。
redis工作场景分析
session存储信息
用来做数据共享分布式
这个需要引入
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId><version>2.6.3</version>
</dependency>
之后设置一下配置
# session 失效时间
session:timeout: 86400store-type: redis
之后我们例如在用户登陆的时候
/*** 用户登录*/
@Override
public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {//登陆逻辑....//获得了登陆的用户信息//模拟User safetyUser=null;//记录sessionHttpSession session = request.getSession();session.setAttribute(USER_LOGIN_STATE, safetyUser);return safetyUser;
}
可以设置这个session
这个可以用来做,例如获得当前用户
/*** 获取当前用户**/
@GetMapping("/current")
public BaseResponse<User> getCurrentUser(HttpServletRequest request) {Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);return ResultUtils.success(userObj);
}
等等操作,你可以理解为,她在redis里面存放着一个用户信息。你需要的时候可以随时的去取出来这个信息
分布式锁
这个推荐用的是redisson
Redisson 是一个 java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis,完全感知不到 Redis 的存在。
可以看下面的俩个事例
// list,数据存在本地 JVM 内存中
List<String> list = new ArrayList<>();
list.add("yupi");
System.out.println("list:" + list.get(0));list.remove(0);// 数据存在 redis 的内存中
RList<String> rList = redissonClient.getList("test-list");
rList.add("xiaou");
System.out.println("rlist:" + rList.get(0));
rList.remove(0);
void testWatchDog() {RLock lock = redissonClient.getLock("test:precachejob:docache:lock");try {// 只有一个线程能获取到锁if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {// todo 实际要执行的方法doSomeThings();System.out.println("getLock: " + Thread.currentThread().getId());}} catch (InterruptedException e) {System.out.println(e.getMessage());} finally {// 只能释放自己的锁if (lock.isHeldByCurrentThread()) {System.out.println("unLock: " + Thread.currentThread().getId());lock.unlock();}}
}
限流
这个也推荐使用redisson
public void doRateLimit(String key) {RRateLimiter rRateLimiter = redissonClient.getRateLimiter(key);//rate是每个时间单位允许访问几次 rateInterval就是时间单位rRateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);//每当一个操作来了之后,去请求一个令牌boolean canOp = rRateLimiter.tryAcquire(1);if (!canOp) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "当前访问过于频繁");}
}
抽奖
/*** 抽奖* @return*/
@GetMapping("/lucky-draw")
public String luckyDraw() {try {// 获取奖池和参与者列表RList<String> prizePool = redissonClient.getList("prize_pool");RList<String> participants = redissonClient.getList("participants");// 进行抽奖String winner = drawWinner(prizePool, participants);// 返回抽奖结果return " The winner is: " + winner;} finally {// 关闭 Redisson 客户端连接redissonClient.shutdown();}
}
总结
当然除了这些,还有很多使用redis的场景,欢迎各位来补充。
文中所有的源码在如下仓库xiaou61/xiaou-easy-code: 前后端通用解决方案 springboot vue react 原生js (github.com)
文章也会在这里同步发送Xiaou-EasyCode-Docs (xiaou61.top)
这个项目是我自己开发的一个前后端通用解决方案的一个项目,致力于各种工作中常用的代码的demo展示。目前github已有400+star 如果你有兴趣,可以去里面提出你的issue或者是pr 共同来完善这个项目。