Redis客户端之Redission(Redis篇二)

5 篇文章 0 订阅

title: Redission
date: 2021-05-04 23:18:15
tags: Redis


Redis 第二篇 Redission

上篇在写客户端的时候提到了我用的比较多的Redission,这节就顺着整理一下。

spring-boot-data-redis 默认使用 Lettuce 客户端操作数据。但Reddissin 很强大,它提供的功能远远超出了一个 Redis 客户端的范畴,使用它来替换默认的 Lettuce。在可以使用基本 Redis 功能的同时,也能使用它提供的一些高级服务:

  • 远程调用
  • 分布式锁
  • 分布式对象、容器

简单讲一下分布式:分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。(演变过程:单机结构 -> 集群结构 -> 分布式结构)

引入依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.13.0</version>
    </dependency>
</dependencies>
application.properties配置
//redis启动主机ip
spring.redis.host=
//redis端口号
spring.redis.port=
//redis登录密码
spring.redis.password=

两个常用应用

一.分布式ID

ID 是数据的唯一标识,传统的做法是利用 UUID 和数据库的自增 ID。

但由于 UUID 是无序的,不能附带一些其他信息。比如需要生成员工号,这个一般是用数据库递增, UUID 是无法完成这个需求的。

再来说自增 ID,随着业务的发展,数据量会越来越大,需要对数据进行分表,甚至分库。分表后每个表的数据会按自己的节奏来自增,这样会造成 ID 冲突,这时就需要一个单独的机制来负责生成唯一 ID。

举例:淘宝的订单号

代码实现

import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

@Controller
public class AutoIdController {

    @Autowired
    RedissonClient redissonClient;

    @GetMapping("/getautoid")
    @ResponseBody
    public String getAutoId() {

        //格式化格式为年月日
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        //获取当前时间
        String now = LocalDate.now().format(dateTimeFormatter);
        //通过redis的自增获取序号
        RAtomicLong atomicLong = redissonClient.getAtomicLong(now);
        atomicLong.expire(1, TimeUnit.DAYS);
        //拼装订单号
        return now + "" + atomicLong.incrementAndGet();

    }
}

运行结果

202105041

嫌不够长,还可以这样String.format("%08d", xxx);%04d 表示输出时指定格式为使用 0 在左侧补齐至 8 位。

        return now + "" + String.format("%08d",atomicLong.incrementAndGet());

运行结果

202105040000002

二.分布式锁

锁,在 Java 中 synchronized 关键字很常见,这些都是本地锁,只能解决一台服务器并发问题。

但是随着业务量不断增大,单机结构不满足那么大的访问量,需要变成集群或者分布式结构,因此无法保证某个数据的改变是同一台服务器操作的。我们需要的是一个能锁所有服务器的锁,这时就需要分布式锁

实现Redis分布 三步
取得锁
//CUSTOM_NAME 自定义锁名称字符串,一般是跟业务相关的名称
 //Rlock 继承于 java.util.concurrent.locks.Lock;(又是Lock类!!)
 RLock rLock = redissonClient.getLock("CUSTOM_NAME")
上锁
/**
 * 上锁过程
 * 两种常用上锁方式tryLock()或者lock()
 */
 rLock.tryLock();
解锁(有上锁就必须解锁,否则会导致死锁,系统也就卡死了。)
//解锁
 rLock.unlock();

代码实操,模拟商品购买

`package com.example.demo;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@Controller
public class ProductController {

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    private static List<Product> products = new ArrayList<>();

    private static Logger logger = LoggerFactory.getLogger(ProductController.class);

    /**
     * 初始化数据
     */
    @PostConstruct
    private void init() {
        products.add(new Product("1"));
        products.add(new Product("2"));
    }

    @PostMapping("/purchase")
    @ResponseBody
    public String purchase() {

        //获取分布式锁
        RLock transferLock = redissonClient.getLock("PURCHASE");

        transferLock.lock();
        //业务逻辑卸载try...catch中 ,finally最后一定要释放锁
        try {
            //尝试获取锁
            Product product = findById("1"); //为了方便测试,直接写死,实际商品代码应有用户post
            if (product.getStock() < 1) {
                return "商品已经卖完啦!!!";
            }

            product.setStock(product.getStock() - 1);
            updateProduct(product);
            return "商品购买成功!!!";

        } catch (Exception e) {
            logger.error("",e);
        } finally {
            // 无论是否出现异常,一定解锁
            transferLock.unlock();
        }

        return "商品购买失败";

    }

    /**
     * 根据id查询商品
     *
     * @param id 唯一id
     * @return Product
     */
    private Product findById(String id) {
        for (Product product : products) {
            if (product.getId().equals(id)) {
                return product;
            }
        }
        return null;
    }

    private Product updateProduct(Product product) {

        for (int i = 0; i < products.size(); i++) {
            if (products.get(i).getId().equals(product.getId())) {
                products.set(i, product);
                return product;
            }
        }
        return null;
    }

}

Product类

package com.youkeda.app.model;

/**
 * TODO
 *
 * @author zr
 * @date 2020/6/2, 周二
 */
public class Product {

    /**
     * 商品唯一Id
     */
    private String id;

    /**
     * 库存
     */
    private Long stock = 2L;  //方便测试,请求到第三次时应该为购买失败

    public Product(final String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(final String id) {
        this.id = id;
    }

    public Long getStock() {
        return stock;
    }

    public void setStock(final Long stock) {
        this.stock = stock;
    }
}

请求三次时

在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值