Redis常见使用场景
发布日期:2021-05-27 02:54:09
浏览次数:17
分类:技术文章
本文共 12339 字,大约阅读时间需要 41 分钟。
在日常开发中,多多少少都在使用redis中间件,比如用作缓存,分布式锁,唯一id、消息通知等,现将用到的场景记录下来,后续会更新哦。
string类型
``` set key value //存入键值对 get key value //根据键获取值 del key //删除 expire key timeout // 为key设置一个超时时间,超过时间会自动释放 setnx key value //当且仅当key不存在时,set一个key为value的字符串,返回1;若key存在,则什么都不做,返回0 incr key //将key中存储的值加一 decr key //将key中存储的值减一 ``` 分布式锁 ``` setnx order_lock true //返回1代表获取锁成功 setnx order_lock true //返回0代表获取锁失败 del order_lock //执行完业务释放锁 好比:张三去上厕所,看厕所门锁着,他就不进去了,厕所门开着他才进入; 问题1,若redis因为宕机或出现异常未释放锁,就造成了死锁;可以通过设置过期时间解决未释放锁的情况,但需要组合命令set key value ex seconds nx;问题2,一个业务执行时间很长,锁已经自动过期了,别人获取到了锁,但是当业务执行完之后直接释放了锁,这时就可能删除了别人的锁,可以通过在加锁的时候设置一个随机值,在删除锁的时候进行对比,若是自己的锁才删除; redis实现分布式锁的方式为去插入一条占位数据;遇到宕机情况,redis需要等到设置的过期时间到了后自动释放锁;redis在没抢占到锁的情况时一般会去自旋获取锁; ``` 实现思想 ``` 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断 获取锁的时间设置一个获取的超时时间,若超过这个时间则放弃获取锁 释放锁的时候,通过UUID判断是不是该锁,若是该锁则执行delete进行锁释放 ``` 示例,在指定时间acquireTimeout内进行秒杀活动,每个抢到订单的需要在指定时间expire内完成支付,如下:public class RedisLock { private final JedisPool jedisPool; public RedisLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 加锁 * * @param keyName 锁的key * @param acquireTimeout 获取超时时间 * @param expire 锁的过期时间 * @return 锁标识 */ public String lockWithTimeout(String keyName, long acquireTimeout, long expire) { // 随机生成一个value String value = UUID.randomUUID().toString(); // 锁名,即key值 String lockKey = "lock:" + keyName; // 锁的超时时间,上锁后超过此时间则自动释放锁 int lockExpire = (int) (expire / 1000); // 获取锁的超时时间,超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; Jedis jedis = null; try { jedis = jedisPool.getResource(); while (System.currentTimeMillis() < end) { Long setnx = jedis.setnx(lockKey, value); if (setnx == 1) { jedis.expire(lockKey,lockExpire); return value; } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return null; } /** * 释放锁 * * @param keyName 锁的key * @param identifier 释放锁的标识 * @return */ public boolean releaseLock(String keyName, String identifier) { String lockKey = "lock:" + keyName; boolean retFlag = false; Jedis jedis = null; try { jedis = jedisPool.getResource(); while (true) { // 监视lock,准备开始事务 jedis.watch(lockKey); // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (identifier.equals(jedis.get(lockKey))) { Transaction transaction = jedis.multi(); transaction.del(lockKey); List
public class DemoService { private static JedisPool jedisPool = null; static { JedisPoolConfig config = new JedisPoolConfig(); // 设置最大连接数 config.setMaxTotal(200); // 设置最大空闲数 config.setMaxIdle(8); // 设置最大等待时间 config.setMaxWaitMillis(1000 * 100); jedisPool = new JedisPool(config, "127.0.0.1", 6379, 3000); } RedisLock redisLock = new RedisLock(jedisPool); int n = 500; public void seckill() { // 返回锁的value值,供释放锁时候进行判断 String indentifier = null; indentifier = redisLock.lockWithTimeout("resource", 5000, 1000); System.out.println(Thread.currentThread().getName() + "获得了锁"); if (indentifier != null) { // 获取到锁后才可以进行操作(只有占用厕所,才可以如厕),具体能够有几个操作需要看acquireTimeout、expire System.out.println(--n); redisLock.releaseLock("resource", indentifier); } }}
public class DemoThread extends Thread { private DemoService demoService; public DemoThread(DemoService demoService) { this.demoService = demoService; } @Override public void run() { demoService.seckill(); } public static void main(String[] args) { DemoService demoService = new DemoService(); for (int i = 0; i < 50; i++) { DemoThread demoThread = new DemoThread(demoService); demoThread.start(); } }}
结果如下:
Thread-7获得了锁
499 Thread-49获得了锁 498 Thread-50获得了锁 497...
springboot继承redis中实现代码如下:
@Componentpublic class RedisLockService { @Autowired private RedisTemplate redisTemplate; /** * 加锁 * * @param keyName 锁的key * @param acquireTimeout 获取超时时间 * @param expire 锁的过期时间 * @return 锁标识 */ public String lockWithTimeout(String keyName, long acquireTimeout, long expire) { // 随机生成一个value String value = UUID.randomUUID().toString(); // 锁名,即key值 String lockKey = "lock:" + keyName; // 锁的超时时间,上锁后超过此时间则自动释放锁 int lockExpire = (int) (expire / 1000); // 获取锁的超时时间,超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < end) { Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, value, lockExpire, TimeUnit.SECONDS); if (ifAbsent) { return value; } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return null; } /** * 释放锁 * * @param keyName 锁的key * @param identifier 释放锁的标识 * @return */ public boolean releaseLock(String keyName, String identifier) { String lockKey = "lock:" + keyName; boolean retFlag = false; Object value = redisTemplate.opsForValue().get(lockKey); if (identifier.equals(value)) { retFlag = redisTemplate.delete(keyName); } return retFlag; }}
@Componentpublic class DemoSkillService { int n = 500; @Autowired private RedisLockService redisLockService; public void seckill() { // 返回锁的value值,供释放锁时候进行判断 String indentifier = null; indentifier = redisLockService.lockWithTimeout("resource", 5000, 1000); if (indentifier != null) { // 获取到锁后才可以进行操作(只有占用厕所,才可以如厕),具体能够有几个操作需要看acquireTimeout、expire System.out.println(Thread.currentThread().getName() + "获得了锁"); System.out.println(--n); redisLockService.releaseLock("resource", indentifier); } }}
结果如下;
Thread-154获得了锁
484 Thread-153获得了锁 483 Thread-185获得了锁 482 Thread-191获得了锁 481 Thread-161获得了锁 480 Thread-196获得了锁 479 Thread-194获得了锁 478
redis唯一编号使用场景,在分布式开发中可以通过redis的自增自减操作生成唯一id作为主键或编码来使用,由于是顺序数组组成,后续使用方便快捷。平常开发中也会使用雪花算法生产唯一id作为主键使用。
代码如下:
public interface OrderService { public String orderId();}@Servicepublic class OderServiceImpl implements OrderService { @Autowired private RedisTemplate redisTemplate; @Override public String orderId() { String key = "order:id"; String prefix = getPrefix(); Long id = redisTemplate.opsForValue().increment(key); System.out.println("prefix:" + prefix); System.out.println("id:" + prefix + id); return prefix + id; } private String getPrefix() { LocalDateTime now = LocalDateTime.now(); int year = now.getYear(); int month = now.getMonthValue(); int day = now.getDayOfYear(); // 处理 return String.valueOf(year) + String.valueOf(month) + String.valueOf(day); }}
单位测试代码部分如下:
private static final int num = 100; private CountDownLatch countDownLatch = new CountDownLatch(num); @Autowired private OrderService orderService; @Test void contextLoads() { for (int i = 0; i{ try { countDownLatch.await(); orderService.orderId(); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); countDownLatch.countDown(); } }
结果如下:
prefix:202119 id:2021191081 id:2021191079 prefix:202119 id:2021191077 prefix:202119 id:2021191076 prefix:202119 id:2021191075 id:2021191073redis消息通知场景,部分代码如下:
@Configurationpublic class RedisListenerConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; }}
@Componentpublic class RedisTask extends KeyExpirationEventMessageListener { public RedisTask(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { // 接受事件后回调 String channel = new String(message.getChannel(), StandardCharsets.UTF_8); String key = new String(message.getBody(), StandardCharsets.UTF_8); System.out.println("key:" + key + ",chanel:" + channel); // 根据key进行处理 }}
分布式锁应用案例:
public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) { SeckillActivityRequestVO response; String key = "key:" + request.getSeckillId; try { Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent(key, "val", 10, TimeUnit.SECONDS); if (lockFlag) { // HTTP请求用户服务进行用户相关的校验 // 用户活动校验 // 库存校验 Object stock = redisTemplate.opsForHash().get(key+":info", "stock"); assert stock != null; if (Integer.parseInt(stock.toString()) <= 0) { // 业务异常 } else { redisTemplate.opsForHash().increment(key+":info", "stock", -1); // 生成订单 // 发布订单创建成功事件 // 构建响应VO } } } finally { // 释放锁 stringRedisTemplate.delete("key"); // 构建响应VO } return response;}//改进public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {SeckillActivityRequestVO response; String key = "key:" + request.getSeckillId(); String val = UUID.randomUUID().toString(); try { Boolean lockFlag = distributedLocker.lock(key, val, 10, TimeUnit.SECONDS); if (!lockFlag) { // 业务异常 } // 用户活动校验 // 库存校验,基于redis本身的原子性来保证 Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1); if (currStock < 0) { // 说明库存已经扣减完了。 // 业务异常。 log.error("[抢购下单] 无库存"); } else { // 生成订单 // 发布订单创建成功事件 // 构建响应 } } finally { distributedLocker.safedUnLock(key, val); // 构建响应 } return response;}//改进// 通过消息提前初始化好,借助ConcurrentHashMap实现高效线程安全private static ConcurrentHashMapSECKILL_FLAG_MAP = new ConcurrentHashMap<>();// 通过消息提前设置好。由于AtomicInteger本身具备原子性,因此这里可以直接使用HashMapprivate static Map SECKILL_STOCK_MAP = new HashMap<>();...public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {SeckillActivityRequestVO response; Long seckillId = request.getSeckillId(); if(!SECKILL_FLAG_MAP.get(requestseckillId)) { // 业务异常 } // 用户活动校验 // 库存校验 if(SECKILL_STOCK_MAP.get(seckillId).decrementAndGet() < 0) { SECKILL_FLAG_MAP.put(seckillId, false); // 业务异常 } // 生成订单 // 发布订单创建成功事件 // 构建响应 return response;}
超卖原因:虽然采用了setnx key value [EX seconds] [PX milliseconds] [NX|XX]的方式,但是如果线程A执行的时间较长没有来得及释放,锁就过期了,此时线程B是可以获取到锁的。当线程A执行完成之后,释放锁,实际上就把线程B的锁释放掉了。这个时候,线程C又是可以获取到锁的,而此时如果线程B执行完释放锁实际上就是释放的线程C设置的锁。这是超卖的直接原因。
转载地址:https://blog.csdn.net/leijie0322/article/details/112394653 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
很好
[***.229.124.182]2024年09月04日 13时33分43秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
mysql编写函数
2019-05-24
面试笔试题之hql
2019-05-24
sql函数之cast()
2019-05-24
hql中substr函数截取字符串匹配
2019-05-24
mysql之指定ip、用户、数据库权限
2019-05-24
zookeeper的读和写数据流程(有图欧)
2019-05-24
bin/schematool -dbType mysql -initSchema HiveMetaException: Failed to get schema version.
2019-05-24
flink知识总结
2019-05-24
mysql之部门工资前三的所有员工
2019-05-24
flink之检查点(checkpoint)和保存点(savepoint)的区别
2019-05-24
Linux系统编程---进程I/O
2019-05-24
spring学习知识补充
2019-05-24
杂文之生成随机字符串
2019-05-24
springBoot基础(一)
2019-05-24
springBoot基础(二)
2019-05-24
在springBoot中使用Mapper类问题
2019-05-24
filebeat___log -input
2019-05-24
GitHub使用
2019-05-24
关于学习Java的一点点心得。附Dos命令的基操
2019-05-24
SpringCloud详细教程3-Eureka服务注册中心
2019-05-24