第七章-springboot整合redis

飞一样的编程
飞一样的编程
擅长邻域:Java,MySQL,Linux,nginx,springboot,mongodb,微信小程序,vue

分类: springboot 专栏: springboot3.0新教材 标签: 整合redis

2024-01-08 15:33:49 2415浏览

整合redis

redis简介

Redis是一款基于key-value的内存数据存储系统,有以下一些优点: 速度快:Redis非常快,每秒可执行大约110000次的设置(SET)操作,每秒大约可执行81000次的读取/获取(GET)操作。

支持丰富的数据类型:Redis支持五种数据类型: string (字符串) , hash(哈希) , list(列表) , set(集合)及zset(sorted set:有序集合)。

操作具有原子性:所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新的值。

应用广泛:可用于缓存,消息队列(Redis本地支持发布/订阅),应用程序中的任何短期数据如web应用程序中的会话(Session)、网页计数、排行榜等。

redis的安装与运行

安装windows版本的Redis即可,另外可以下载Redis图形化客户端工具,方便直观查看数据库信息,这里下载的是RedisDesktopManager。

启动redis:双击redis-server.exe就行

疑问:那如果我要设置密码启动呢?

修改redis.windows.conf里的配置

然后启动的时候,是在redis安装目录下,cmd里输入redis-server.exe redis.windows.conf回车

redis的常用命令

a. 通用命令

select:选择数据库,Redis默认有16个数据库,编号是0-15,默认当前数据库是0,如果要选择其他编号的数据库则用“select 编号”的命令。

keys:查看键,后面接键的名称或包含通配符*的字符串,如果要查看所有的键,则用 keys *。

exists key:是否存个某个键,key代表键的名称。

del key:删除某个键,key代表键的名称。

flushall:删除所有的键

flushdb:删除当前数据库中的所有Key

b. 值为String类型的有关命令

set key value:添加一个键值对到Redis数据库中,如果键存在,则修改键的值。

mset key1 value1 key2 value…:一次设置多个键值对。

get key:通过键查找值。

mget key1 key2 key3…:一次读取多个键的值。

append key value:追加value值到键原有值上,如果没有这个键,则新建。

c. 值为 List类型的有关命令

lpush key value:添加一个值到列表的头部(左侧添加)。

rpush key value:添加一个值到列表的尾部(右侧添加)。

lrange key start stop:查看列表中索引从start到stop之间的值。

lpop key:返回并删除列表中的头部的值。

rpop key: 返回并删除列表中的尾部的值。

springboot访问redis

Spring Boot访问Redis的技术称为Spring Data Redis,Spring Boot提供了RedisAutoConfiguration自动配置类,该自动配置类检测到spring-boot-start-data-redis依赖包被使用时生效,只需要在Spring Boot中导入spring-boot-start-data-redis依赖包就可以使用Spring Data Redis。Spring Data Redis默认创建了RedisTemplate和StringRedisTemplate两个bean,用户可以从Spring容器中注入并使用它们进行Redis的常用操作。此外用户还可以使用RedisRepository来访问Redis数据库。RedisTemplate提供了操作Redis中的多种数据类型的API,StringRedisTemplate只针对键值都是字符串类型的数据进行操作。RedisTemplate默认使用JdkSerializationRedisSerializer进行序列化,StringRedisTemplate默认使用StringRedisSerializer进行序列化。

RedisAutoConfiguration自动配置类创建RedisTemplate和StringRedisTemplate实例(bean)时使用了RedisProperties类提供的属性,包括Redis服务器地址、端口号、密码等等属性,这些属性很多都有默认值,比如服务器地址默认为localhost、端口号默认为6379,同时该类上面有@ConfigurationProperties(prefix = "spring.redis")注解, 表示可以加载Spring Boot的主配置文件application.properties中前缀为spring.redis的配置信息,这样用户既可以不在application.properties做任何有关Redis的配置,也可以在application.properties中使用spring.redis前缀重新配置Redis。查看RedisProperties源码,可以看到Spring Boot有关Redis的默认配置如下,如果要重新配置,拷贝这些代码到application.properties中并适当修改即可。代码如下:

redis template操作string类型

a. 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

b. 配置application.properties

Spring Boot自动配置了Redis,默认配置可以满足本项目要求,所以这里application.properties不需要任何配置。

c. 创建实体类。注意实体类Book必须实现序列化接口。


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book implements Serializable {
    private Integer id;
    private String name;
    private Double price;
    private String category;
    private Integer pnum;
    private String imgurl;
    private String description;
    private String author;
    private Integer sales;
}

d. 写单元测试类

然后开始直接写单元测试类(完成以下功能)

    //存储字符串类型的key-value
    //通过键获取字符串类型的值
    //删除某个键值对
    //保存一本书
    
    //通过id号查找一本书
    //保存多本书 list
    //查找所有书
    //从多本book集合中找到某个id的书
 	//根据id删除book集合中的某本书
 Book book = new Book(1, "三国演义", 100.00, "文学", 100, "101.png", "四大名著", "罗贯中", 55);


new Book(1,"C语言程序设计",50.0,"计算机",100,"101.jpg","","zhangsan",50),
new Book(2,"java语言程序设计",60.0,"计算机",100,"102.jpg","","zhangsan",50),
new Book(3,"python语言程序设计",70.0,"计算机",100,"103.jpg","","zhangsan",50)
        
      
@SpringBootTest
public class RedisTemplateTest {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;


    //存储字符串类型的key-value
    @Test
    void testKeyV(){
        stringRedisTemplate.opsForValue().set("username","xiaojie");
    }
    //通过键获取字符串类型的值
    @Test
    void getValue(){
        String username = stringRedisTemplate.opsForValue().get("username");

        System.out.println(username);
    }
    //删除某个键值对
    @Test
    void delKey(){
        stringRedisTemplate.delete("username");
    }
    //保存一本书
    @Test
    void saveBook(){
        Book book = new Book(1, "三国演义", 100.00, "文学", 100, "101.png", "四大名著", "罗贯中", 55);
        redisTemplate.opsForValue().set(1,book);
    }
    //通过id号查找一本书
    @Test
    void getBook(){
        Book book = (Book) redisTemplate.opsForValue().get(1);
        System.out.println(book);
    }
    //保存多本书 list
    @Test
    void saveBooks(){
        List<Book> books= Arrays.asList(
                new Book(1,"C语言程序设计",50.0,"计算机",100,"101.jpg","","zhangsan",50),
                new Book(2,"java语言程序设计",60.0,"计算机",100,"102.jpg","","zhangsan",50),
                new Book(3,"python语言程序设计",70.0,"计算机",100,"103.jpg","","zhangsan",50)
        );
        redisTemplate.opsForValue().set("books",books);
    }
    //查找所有书
    @Test
    void searchBooks(){
        System.out.println(redisTemplate.opsForValue().get("books"));
    }

    //从多本book集合中找到某个id的书
    @Test
    void getBookById(){
        Integer id =1;
        List<Book> books =(List<Book>) redisTemplate.opsForValue().get("books");
        Book book = books.get(id - 1);
        System.out.println(book);
    }

    //根据id删除book集合中的某本书
    @Test
    void delBook(){
        Integer id =1;
        List<Book> books =(List<Book>) redisTemplate.opsForValue().get("books");
        books.remove(id-1);
        redisTemplate.opsForValue().set("books",books);
    }
}

e. 解决序列化问题

使用StringRedisTemplate操作的数据没什么问题,但RedisTemplate操作的数据为乱码,无法直接查看,这是因为RedisTemplate默认的序列化为JdkSerializationRedisSerializer,这样对象数据就会使用Java对象流保存为序列化后的字符串(无法直接查看)。对于这种情况,可以通过在配置类中重新创建RedisTemplate的bean,使用Jackson2JsonRedisSerialize替换默认序列化,使对象转换成JSON格式的字符串再进行保存。

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate  redisTemplate( RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        //objectMapper, 必须加,解决查询缓存转换异常的问题
        Jackson2JsonRedisSerializer ser = new Jackson2JsonRedisSerializer(objectMapper,Object.class);
        template.setDefaultSerializer(ser);

        return template;
    }
}

redis template操作redis各种数据类型

RedisTemplate提供了以下5种方法分别操作Redis的5种类型的数据。

(1) opsForValue()方法:操作字符串。

(2) opsForHash()方法:操作散列。

(3) opsForList()方法:操作列表。

(4) opsForSet()方法:操作集合。

(5) opsForZSet()方法:操作有序集合。

1. opsForValue()方法

void set(K key,V value)方法:用于存储键值对,键和值都是字符串,其中值可以是序列化后的对象

void set(K key,V value,long timeout,timeUnit unit)方法:存储键值对的同时规定了失效时间,timeout代表失效时间,unit代表时间单位,可以取以下值:

(1) TimeUnit.DAYS:日

(2) TimeUnit.HOURS:时

(3) TimeUnit.MINUTES:分

(4) TimeUnit.SECONDS:秒

(5) TimeUnit.MILLISECONDS:毫秒

a. 测试案例1:set

@SpringBootTest
class RedisdemoApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void test1() throws InterruptedException { //测试失效时间
        redisTemplate.opsForValue().set("test1", "Test Timeout 5 seconds", 5, TimeUnit.SECONDS);
        System.out.println("第0秒取值:" + redisTemplate.opsForValue().get("test1"));
        Thread.sleep(4000);
        System.out.println("第4秒取值:" + redisTemplate.opsForValue().get("test1"));
        Thread.sleep(2000);
        System.out.println("第6秒取值:" + redisTemplate.opsForValue().get("test1"));
    }
第0秒取值:Test Timeout 5 seconds
第4秒取值:Test Timeout 5 seconds
第6秒取值:null

可见一旦超过时间,就会被redis删掉

b. 测试案例2:getAndSet

V getAndSet(K key,V value)方法:先获取并返回key的旧值,再设置为新值。

   @Test
    void test2() throws InterruptedException { //测试getAndSet方法
        redisTemplate.opsForValue().set("test2", "Test getAndSet1");
        System.out.println(redisTemplate.opsForValue().getAndSet("test2", "Test getAndSet2"));
        System.out.println(redisTemplate.opsForValue().get("test2"));
    }
Test getAndSet1
Test getAndSet2

c. 案例3:append

Integer append(K key,String value)方法:如果key不存在则创建,相当于set方法,如果key存在则追加字符串到未尾。

@Test
    void test3() { //测试append方法
        redisTemplate.setValueSerializer(new StringRedisSerializer());//设置字符串序列化
        redisTemplate.opsForValue().append("test3", "test3"); //首次追加相当于set方法
        System.out.println(redisTemplate.opsForValue().get("test3"));
        redisTemplate.opsForValue().append("test3", " OK!");//追加内容到未尾
        System.out.println(redisTemplate.opsForValue().get("test3"));
    }
test3
test3 OK!

2. opsForHash()方法

opsForHash()方法又提供操作Hash类型的值的方法如下:void put(H h, HK hk,HV hv)方法:用于存储指定键中的字段与值。Map<HK, HV> entries(H h)方法:获取指定键的所有字段与值。

分别存储书的名称与价格信息

 @Test
    void testHash1() { //测试put方法,存入一个键值
        redisTemplate.opsForHash().put("book1", "bookname", "三国演义");
        redisTemplate.opsForHash().put("book1", "price", "69.0");
        System.out.println(redisTemplate.opsForHash().entries("book1")); //读取整个hash类型(键与值)
    }
{bookname=三国演义, price=69.0}

a. putAll

void putAll(H h, java.util.Map<? extends HK, ? extends HV> map)方法:用Map 封装多个字段与值,一次性存储多个字段与值。

  @Test
    void testHash2() { //测试Hash类型,putAll方法,将整个hashmap存入
        Map<String, Object> map = new HashMap();
        map.put("bookname", "西游记");
        map.put("category", "文学");
        redisTemplate.opsForHash().putAll("book2", map);
        System.out.println(redisTemplate.opsForHash().entries("book2")); //读取整个hash类型
    }

b. keys

Set<HK> keys(H h)方法:获取指定键的字段的集合。

@Test
    void testHash3() { //测试keys方法。获取所有键的集合
        redisTemplate.opsForHash().put("book3", "bookname", "红楼梦");
        redisTemplate.opsForHash().put("book3", "price", "89.0");
        System.out.println(redisTemplate.opsForHash().keys("book3")); //读取所有键的集合
    }
[bookname, price]

c. values

List<HV> values(H h)方法:获取指定键的值的集合。

 @Test
    void testHash4() { //测试values方法。获取所有值的集合
        redisTemplate.opsForHash().put("book4", "bookname", "封神榜");
        redisTemplate.opsForHash().put("book4", "price", "79.0");
        System.out.println(redisTemplate.opsForHash().values("book4")); //读取所有值的集合
    }
[封神榜, 79.0]

d. size、hasKey、delete

Long size(H h)方法:返回指定键中字段的数量。

Boolean hasKey(H h, Object o)方法:查找指定键中是否包含某个字段。

Long delete(H h, Object... objects)方法:删除指定键中的某个字段。

@Test
    void testHash5() { //测试delete方法。删除某个键的某个字段,hasKey方法,size方法
        redisTemplate.opsForHash().put("book5", "bookname", "封神榜");
        redisTemplate.opsForHash().put("book5", "price", "89.0");
        System.out.println(redisTemplate.opsForHash().entries("book5"));
        System.out.println(redisTemplate.opsForHash().size("book5"));//字段个数
        System.out.println(redisTemplate.opsForHash().hasKey("book5", "price")); //判断字段是否存在
        redisTemplate.opsForHash().delete("book5", "price"); //删除一个字段
        System.out.println(redisTemplate.opsForHash().hasKey("book5", "price"));//再次判断
        System.out.println(redisTemplate.opsForHash().entries("book5"));
    }
{bookname=封神榜, price=89.0}
2
true
false
{bookname=封神榜}

3. opsForList()方法

a. leftPush:从左侧添加一个元素到列表

Long leftPush(K k, V v):从左侧添加一个元素到列表。

Long leftPushAll(K k, V... vs):从左侧一次添加多个元素到列表。

Long size(K k):获取集合中的元素个数。

java.util.List<V> range(K k,long l, long l1):返回某个列表指定索引区间的元素。索引从0算起,-1表示最右侧第一个元素。

 @Test
    void TestList1() {//测试leftPush方法,leftPushAll方法,range方法,size方法
        redisTemplate.opsForList().leftPush("mylist1", "a");
        redisTemplate.opsForList().leftPush("mylist1", "b");
        redisTemplate.opsForList().leftPush("mylist1", "c");
        System.out.println(redisTemplate.opsForList().size("mylist1"));
        System.out.println(redisTemplate.opsForList().range("mylist1", 0, -1));
        redisTemplate.opsForList().leftPushAll("mylist1", "1","2");//添加多个元素
        String[] str = {"3", "4", "5"};
        redisTemplate.opsForList().leftPushAll("mylist1", str);//使用数组添加多个元素
        System.out.println(redisTemplate.opsForList().size("mylist1"));
        System.out.println(redisTemplate.opsForList().range("mylist1", 0, -1));

    }
3
[c, b, a]
8
[5, 4, 3, 2, 1, c, b, a]

b. rightPushAll:从右侧添加多个元素到列表

Long rightPushAll(K k, V... vs):从右侧一次添加多个元素到列表。

@Test
    void TestList2() {//测试rightPush方法,rightPushAll方法
        redisTemplate.opsForList().rightPush("mylist2", "a");
        redisTemplate.opsForList().rightPush("mylist2", "b");
        redisTemplate.opsForList().rightPush("mylist2", "c");
        System.out.println(redisTemplate.opsForList().size("mylist2"));
        System.out.println(redisTemplate.opsForList().range("mylist2", 0, -1));
        redisTemplate.opsForList().rightPushAll("mylist2", "1","2");//一次添加多个元素
        String[] str = {"3", "4", "5"};
        redisTemplate.opsForList().rightPushAll("mylist2", str);//使用数组一次添加多个元素
        System.out.println(redisTemplate.opsForList().size("mylist2"));
        System.out.println(redisTemplate.opsForList().range("mylist2", 0, -1));

    }
3
[a, b, c]
8
[a, b, c, 1, 2, 3, 4, 5]

c. remove:删除集合中指定数量个元素

【例7-11】 Long remove(K k, long l, Object o):删除集合中指定数量个元素,如果数量为0,则该元素全部从集合删除,如果数量为正数,则从左到右删除,如果为负数,则从右到左删除。

 @Test
    void testList3() { //测试remove
        String[] str = {"10", "20", "30", "10", "20", "30", "10", "20", "30"};
        redisTemplate.opsForList().rightPushAll("mylist3", str);
        System.out.println(redisTemplate.opsForList().range("mylist3", 0, -1));
        redisTemplate.opsForList().remove("mylist3", 0, "10");//全部删除
        System.out.println(redisTemplate.opsForList().range("mylist3", 0, -1));
        redisTemplate.opsForList().remove("mylist3", 2, "20");//从左到右删
        System.out.println(redisTemplate.opsForList().range("mylist3", 0, -1));
        redisTemplate.opsForList().remove("mylist3", -2, "30");//从右到左删
        System.out.println(redisTemplate.opsForList().range("mylist3", 0, -1));
    }
[10, 20, 30, 10, 20, 30, 10, 20, 30]
[20, 30, 20, 30, 20, 30]
[30, 30, 20, 30]
[30, 20]

d. leftPop和rightPop:删除并返回左侧/右侧第一个元素

V leftPop(K k):删除并返回左侧第一个元素。

V rightPop(K k):删除并返回右侧第一个元素。

@Test
    void testList4() {//测试leftPop,rightPop方法
        String[] str = {"a", "b", "c","d","e"};
        redisTemplate.opsForList().rightPushAll("mylist4", str);
        System.out.println(redisTemplate.opsForList().range("mylist4", 0, -1));
        System.out.println(redisTemplate.opsForList().leftPop("mylist4"));//从左侧删
        System.out.println(redisTemplate.opsForList().range("mylist4", 0, -1));
        System.out.println(redisTemplate.opsForList().rightPop("mylist4"));//从右侧删
        System.out.println(redisTemplate.opsForList().range("mylist4", 0, -1));


    }
[a, b, c, d, e]
a
[b, c, d, e]
e
[b, c, d]

e. index:获取指定索引处的元素值 set:为指定索引处的元素设置值,原有值将被覆盖

V index(K k, long l):获取指定索引处的元素值-

void set(K k, long l, V v):为指定索引处的元素设置值,原有值将被覆盖。

@Test
    void testList5(){ //测试set和index方法
        String[] str = {"a", "b", "c"};
        redisTemplate.opsForList().rightPushAll("mylist5", str);
        System.out.println(redisTemplate.opsForList().range("mylist5", 0, -1));
        System.out.println(redisTemplate.opsForList().index("mylist5",1));//获取索引1处的值
        redisTemplate.opsForList().set("mylist5", 1,"d");//将索引1处的值改为b
        System.out.println(redisTemplate.opsForList().range("mylist5", 0, -1));
        System.out.println(redisTemplate.opsForList().index("mylist5",1));

    }
[a, b, c]
b
[a, d, c]
d

4. opsForSet()方法

a. add size members remove pop

Long add(K k, V... vs)方法:添加数据(元素)到集合。

Long size(K k)方法:返回集合的长度,即集合中元素的个数。

java.util.Set<V> members(K k):返回集合中的所有元素。

Long remove(K k, Object... objects):删除元素。

V pop(K k):随机删除。

@Test
    void testSet1(){ //测试添加 删除,size,members方法

        redisTemplate.opsForSet().add("myset1","a");
        redisTemplate.opsForSet().add("myset1","b","c");
        String[] str={"1","2"};
        redisTemplate.opsForSet().add("myset1",str);
        System.out.println("size:"+redisTemplate.opsForSet().size("myset1"));
        System.out.println(redisTemplate.opsForSet().members("myset1"));//查看所有元素
        System.out.println(redisTemplate.opsForSet().remove("myset1","a"));//删除一个元素
        System.out.println(redisTemplate.opsForSet().members("myset1"));
        System.out.println(redisTemplate.opsForSet().remove("myset1",str));//删除多个元素
        System.out.println(redisTemplate.opsForSet().members("myset1"));
        redisTemplate.opsForSet().add("myset1",str);//重新添加
        System.out.println(redisTemplate.opsForSet().members("myset1")); //再次查看
        System.out.println(redisTemplate.opsForSet().pop("myset1"));//随机删除
        System.out.println(redisTemplate.opsForSet().members("myset1"));

    }
size:5
[b, a, 1, c, 2]
1
[1, c, 2, b]
2
[b, c]
[2, b, 1, c]
2
[b, 1, c]

b. move:移动元素到另一个集合

Boolean move(K k, V v,K k1)方法:移动元素到另一个集合。

 @Test
    void testSet2(){ //测试移动
        String[] str1={"1","2","3"};
        String[] str2={"a","b","c"};
        System.out.println(redisTemplate.opsForSet().add("myset2",str1));
        System.out.println(redisTemplate.opsForSet().add("myset3",str2));
        System.out.println(redisTemplate.opsForSet().members("myset2"));
        System.out.println(redisTemplate.opsForSet().members("myset3"));
        redisTemplate.opsForSet().move("myset2","1","myset3");
        System.out.println(redisTemplate.opsForSet().members("myset2"));
        System.out.println(redisTemplate.opsForSet().members("myset3"));


    }
[3, 1, 2]
[c, a, b]
[3, 2]
[c, a, 1, b]

c. scan:遍历集合中所有元素

Cursor<V> scan(K k, ScanOptions scanOptions):遍历集合中所有元素。

  @Test
    void testSet3(){
        String[] str={"a","b","c"};
        redisTemplate.opsForSet().add("myset4",str);
        Cursor<Object> cursor=redisTemplate.opsForSet().scan("myset4", ScanOptions.NONE);
        while(cursor.hasNext()){
            System.out.println(cursor.next());
        }
    }

5. opsForZSet()方法

Boolean add(K k, V v, double v1):添加元素。

Long size(K k):返回集合中元素的个数。

Set<V> range(K k, long l, long l1):返回指定索引范围的元素。

Long rank(K k, Object o):返回某个元素的索引。

@Test
    void testZset1(){
        redisTemplate.opsForZSet().add("zset1","f1",80);
        redisTemplate.opsForZSet().add("zset1","f2",70);
        redisTemplate.opsForZSet().add("zset1","f3",90);
        System.out.println("size:"+redisTemplate.opsForZSet().size("zset1"));
        System.out.println(redisTemplate.opsForZSet().range("zset1",0,-1));
        System.out.println(redisTemplate.opsForZSet().rank("zset1","f1"));



    }
size:3
[f2, f1, f3]
1

a. score:返回某个元素的分数

Double score(K k, Object o):返回某个元素的分数。

Set<V> rangeByScore(K k, double v, double vl):返回指定分数范围的元素。

Long count(K k, double v, double vl):返回指定分数范围的元素的个数。

  @Test
    void testZset2(){
        redisTemplate.opsForZSet().add("zset2","f1",80);
        redisTemplate.opsForZSet().add("zset2","f2",70);
        redisTemplate.opsForZSet().add("zset2","f3",90);
        redisTemplate.opsForZSet().add("zset2","f4",60);
        System.out.println(redisTemplate.opsForZSet().score("zset2","f1"));
        System.out.println(redisTemplate.opsForZSet().range("zset2",0,-1));

        System.out.println(redisTemplate.opsForZSet().rangeByScore("zset2",70,90));
        System.out.println(redisTemplate.opsForZSet().count("zset2",70,90));


    }
80.0
[f4, f2, f1, f3]
[f2, f1, f3]
3

b. remove:删除指定元素

 @Test
    void testZset3(){
        redisTemplate.opsForZSet().add("zset3","f1",40);
        redisTemplate.opsForZSet().add("zset3","f2",50);
        redisTemplate.opsForZSet().add("zset3","f3",60);
        redisTemplate.opsForZSet().add("zset3","f4",70);
        redisTemplate.opsForZSet().add("zset3","f5",80);
        redisTemplate.opsForZSet().add("zset3","f6",90);
        redisTemplate.opsForZSet().add("zset3","f7",100);
        System.out.println(redisTemplate.opsForZSet().range("zset3",0,-1));
        redisTemplate.opsForZSet().remove("zset3","f1");
        System.out.println(redisTemplate.opsForZSet().range("zset3",0,-1));
        redisTemplate.opsForZSet().removeRange("zset3",0,1);
        System.out.println(redisTemplate.opsForZSet().range("zset3",0,-1));
        redisTemplate.opsForZSet().removeRangeByScore("zset3",70,80);
        System.out.println(redisTemplate.opsForZSet().range("zset3",0,-1));

    }
[f1, f2, f3, f4, f5, f6, f7]
[f2, f3, f4, f5, f6, f7]
[f4, f5, f6, f7]
[f6, f7]

如果是降序排列呢?

redisTemplate.opsForZSet().reverseRange("zset3",0,-1)

redis实现分布式session共享

如果服务端做了负载均衡,服务端同时部署了多个服务器,当用户在服务器1上进行了登录,然后下次访问的是服务器2,则有可能不能识别用户的登录状态,这时就要通过共享Session来解决,其思路是将Session放在多个服务器之外的共同的Redis中,让多个服务器共享。

a. 依赖

 <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
<!--            <version>2.5.0</version>-->
        </dependency>

b. 启动类添加@EnableRedisHttpSession注解。

c. 创建控制器

d. 多环境配置

删除application.properties文件,

创建application-dev.properties文件表示开发环境,配置如下:

server.port=8081

创建application-prod.properties文件表示生产环境,配置如下:

server.port=8082

e. 运行测试

先打包项目,然后切换到jar包所在目录的命令提示符,运行两个服务器端程序,模拟两个不同的服务器,分别输入两条命令如下:

服务器安装jdk17

wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz

配置环境变量

export JAVA_HOME=/path/to/jdk17
export PATH=$PATH:$JAVA_HOME/bin

使其生效

source /etc/profile
nohup java -jar redisSession-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev &
nohup java -jar redisSession-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod &

#&表示让项目在后台运行

java -jar xxx.jar & 

#nohup表示当窗口(xshell)关闭时服务不挂起,继续在后台运行

nohup java -jar xxx.jar &
nohup java -jar -Dserver.port=8088 xxx.jar & 


也可以换个思路解决

复制一个出来,相当于集群,两个项目源码是一模一样的,只是激活的profiles不一样(一个是dev的,一个是prop的)

f. 配置Nginx

浏览器访问localhost:80 时,并多次刷新,发现将轮流实际访问127.0.0.1:8081和127.0.0.1:8082,实现了反向代理和负载均衡的功能

新闻阅读与点赞次数实战

实战说明:首页是新闻列表,点击任一标题,可以进入该新闻详情页面,详情页面显示该条新闻阅读次数,点赞次数,初次进入阅读次数为1,点赞数为0,提供一个按钮可以点赞,点赞完后点赞数立即更新,刷新页面,阅读次数和点赞次数都刷新。阅读和点赞次数都保存到redis内存数据库中,速度快,但每隔一分钟都将最新数据持久化保存到mysql数据库中进行持久化。

考虑用redis自增自减

自增(increment):使用opsForHash().increment(key, hashKey, delta)方法来对指定的hash字段进行自增操作。其中,key表示存储在Redis中的键名称; hashKey表示要自增的字段名称; delta表示每次自增的值。如果该字段不存在或者为空,则会将其初始化为0后再进行自增操作。

a. 添加依赖

 <!--quartz相关依赖 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
        </dependency>

b. 创建NewsController控制器

主要提供访问首页的功能,查看新闻详情的功能,查看详情时把该新闻id存储在redis中包含阅读次数和点赞次数的hash类型数据读取出来,阅读次数还要加1,存入model域供前端展示,并更新到redis。还有点赞功能,从redis中提取数据,然后点赞次数加1

@Controller
public class NewsController {

    @Autowired
    RedisTemplate redisTemplate;
    //更新浏览量
    @GetMapping("/detail/{id}")
    public String see(@PathVariable Integer id, Model model){

        //阅读量自增1
        redisTemplate.opsForHash().increment("news_" + id, "read", 1);
        //查询出数据放到model中,方便前端页面展示点赞量和阅读量
        Map map = redisTemplate.opsForHash().entries("news_" + id);
        model.addAllAttributes(map);

        return "news"+id;

    }
    //点赞功能
    @RequestMapping("/addZan/{id}/{opType}")
    @ResponseBody
    public Long addZan(@PathVariable Integer id ,@PathVariable Integer opType){
        Long countZan;
        if (opType==1) {//点赞
            countZan = redisTemplate.opsForHash().increment("news_" + id, "countZan", 1);


        }else{//取消点赞
            countZan=redisTemplate.opsForHash().increment("news_"+id,"countZan",-1);

       }
        return countZan;
    }
}

c. 定时任务

Redis中的数据每隔一分钟要保存到数据库用到Spring的定时任务,

创建NewJob类,创建SaveNewsData方法,在方法前面添加@Scheduled注解实现每隔一分钟执行该方法功能,此外启动类上面要加上@EnableScheduling注解,表示开启定时任务

@Slf4j
@Component
public class NewsJob {
    @Autowired
    private RedisTemplate redisTemplate;

    @Scheduled(cron = "0 0/1 * * * ? ")
    public void saveNew(){
        Set<String> keys = redisTemplate.keys("news_*");
        for(String key:keys){
            Map  map=redisTemplate.opsForHash().entries(key);
            String id=key.substring(5);//获取key中包含的id号,key是news_+id的格式
            //然后就可调用业务层将这些数据update到数据库,自行实现。
            log.info("编号:"+id+",阅读次数:"+map.get("read")+",点赞次数:"+map.get("countZan"));
        }

        log.info("以上数据已保存到数据库");
    }
}

补充:cron表达式(七子表达式)

d. 创建视图

📎jquery-1.12.4.js

前端页面

<h1>新闻列表</h1>

<ul>
    <li>
        <a th:href="@{/detail/1}">新闻1</a>

    </li>
    <li>
        <a th:href="@{/detail/2}">新闻2</a>
    </li>
</ul>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>warmer-redis</title>
    <script type="text/javascript" src="/js/jquery-1.12.4.js"></script>
    <script>
        function zan(id,opType){
            $.ajax({
                url:"/addZan/"+id+"/"+opType,
                type:"get",
                success:function(data){
                    $("#countZan").text(data);
                }
            });
        }

    </script>
</head>
<body>
<h1 style="text-align: center">新闻1</h1>
阅读数<font color="red" th:text="${read}" ></font>&nbsp;&nbsp; 点赞数<font id="countZan" color="red" th:text="${countZan==null?0:countZan}"></font>&nbsp;&nbsp;
<input type="button" onclick="zan(1,1)" value="点赞"/>
<input type="button" onclick="zan(1,0)" value="取消点赞"/>

</body>
</html>

e. 测试

浏览新闻2,结果类似。此外观察控制台,每隔一分钟会提示最新数据保存到数据库

使用RedisRepository访问redis

Spring Data推出了操作Redis的RedisRespository方式,提供类似Spring Data JPA一样的接口,使得操作Redis变得非常简单。只要在接口中继承CrudRepository接口,就能跟Spring Data JPA一样,其本的增删改操作不需要定义任何方法。

a. 加@RedisHash注解

注意必须要加@RedisHash注解,用于指定操作实体类对象在Redis数据库中的存储空间,此处表示针对Book实体类的数据操作都存储在Redis数据库中名为books的存储空间下

@Data
@AllArgsConstructor
@NoArgsConstructor
//@RedisHash(value = "bookinfo",timeToLive = 60)
@RedisHash(value = "bookinfo")
public class BookInfo {
    @Id
    private int id;
    @Indexed
    private String name;
    private String category;
    private String author;
    private double price;
}

b. dao层

@Repository
public interface BookInfoDao  extends CrudRepository<BookInfo,Integer> {
    Iterable<BookInfo> findByName(String name);
}

c. 测试

@SpringBootTest
public class RedisRepository {
    @Autowired
    BookInfoDao bookInfoDao;

    @Test //添加图书
    void testSaveBook() {
        BookInfo book1=new BookInfo(1,"西游记","文学","吴承恩",88);
        bookInfoDao.save(book1);
        BookInfo book2=new BookInfo(2,"三国演义","文学","罗贯中",78);
        bookInfoDao.save(book2);
        BookInfo book3=new BookInfo(3,"水浒传","文学","施耐俺",68);
        bookInfoDao.save(book3);
        BookInfo book4=new BookInfo(4,"三国志","文学","佚名",78);
        bookInfoDao.save(book4);
        System.out.println("添加成功!");
    }

    @Test //查询所有图书
    void testFindAllBooks() {
        Iterable<BookInfo> iterable=bookInfoDao.findAll();
        Iterator<BookInfo> it=iterable.iterator();
        while(it.hasNext()){
            BookInfo book= it.next();
            System.out.println(book);
        }
    }


    @Test //根据Id号图书
    void testFindBookById() {
        BookInfo book=bookInfoDao.findById(1).get();
        System.out.println(book);
    }

    @Test //根据Name查询图书
    void testFindBooksByName() {
        Iterable<BookInfo> iterable=bookInfoDao.findByName("三国志");
        Iterator<BookInfo> it=iterable.iterator();
        while(it.hasNext()){
            BookInfo book= it.next();
            System.out.println(book);
        }
    }

    @Test //修改图书
    void testUpdateBook() {
        BookInfo book=bookInfoDao.findById(1).get();
        book.setPrice(98);
        bookInfoDao.save(book);
        System.out.println("修改成功!");
    }

    @Test //删除图书
    void testDeleteBook() {
        bookInfoDao.deleteById(2);
        System.out.println("删除成功!");
    }
}

Linux服务器里redis安装和启动

1、解压安装包

2、进入到安装目录

3、make编译或者 make MALLOC=libc

如果报错的话,检查一下是否安装gcc

4、修改redis的配置文件redis.conf,

  • 确保宿主机能连接到虚拟主机里的redis

本机redis客户端连接虚拟主机里的Redis,需要修改下redis的配置文件,bind 默认是127.0.0.1要将其改为0.0.0.0

  • 修改成密码启动,

  • 设置以后台的形式运行

5、启动redis

进入redis安装目录的src下输入./redis-server ../redis.conf启动redis

搭建redis集群

什么是redis集群呢?

Redis3.0加入了Redis的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。 Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。

首先在Linux中搭建好Redis集群,

redis的三种集群方式

redis有三种集群方式:主从复制,哨兵模式和集群

1.主从复制

    • 主从复制原理:

●从服务器连接主服务器,发送SYNC命令;

●主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

●主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期问继续记录被执行的写命令;

●从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;

●主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; .

●从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令; (从服务器初始化完成)

●主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)

    • 主从复制优缺点:

优点:

●为了分载Master的读操作压力, Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成

●Slave同样可以接受其它Slaves的连接和同步请求 ,这样可以有效的分载Master的同步压力。

●支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

● Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。

●Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据

缺点:

●Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写清求失败,需要等待机器重启或者手动切换前端的IP才能恢复。

●主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题 ,降低了系统的可用性。

●Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

2.哨兵模式

当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。为此, Redis2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。

哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。

(1)监控主服务器和从服务器是否正常运行,

(2)主服务器出现故障时自动将从服务器转换为主服务器。

    • 哨兵模式的优缺点

优点:

●哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。

●主从可以自动切换,系统更健壮,可用性更高。

缺点:

●Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

3.Redis-Cluster集群

redis的哨兵模式基本已经可以实现高可用,读写分离, 但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。

Redis-Cluster采用无中心结构,它的特点如下:

●所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

●节点的fail是通过集群中超过半数的节点检测失效时才生效。

●客户端与redis节点直连,不需要中间代理层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

工作方式:

在redis的每一个节点上, 都有这么两个东西, 一个是插槽(slot) ,它的的取值范围是:0-16383, 还有一个就是cluster ,可以理解为是-个集群管理的播件。当我们的存取的key到达的时候, redis会根据crc16的算法得出一个结果, 然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

为了保证高可用, redis-cluster体群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。

开始搭建-redis版本5.0.9

在redis的安装目录下创建一个存放集群的文件夹,比如我建的是rediscluster

mkdir -p rediscluster

接着进入到rediscluster这个文件夹中创建7000——7005这六个文件夹,用来模拟集群中6个节点

mkdir -p 7000 7001 7002 7003 7004 7005

然后,把redis安装目录下的redis.conf分别复制进刚创建的6个文件夹中。(建议用最原始的redis.conf,而不是改动过的,因为要改的地方比较多,所以干脆直接在redis.conf里追加)

修改每个节点里的redis.conf,参考下面的,直接复制粘贴进去后,改改端口就行,比如有的是7000,有的端口是7001,还有那个bind的ip,改成你自己的

bind 192.168.56.15
protected-mode no
port 7000
daemonize yes
pidfile /var/run/redis_7000.pid
dir /opt/software/redis-5.0.9/rediscluster/7000/
appendonly yes
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
masterauth 123456
requirepass 123456

接着就是启动6个redis服务器

./redis-server ../rediscluster/7000/redis.conf

接着启动 Redis Cluster 集群

./redis-cli --cluster create 192.168.56.15:7000 192.168.56.15:7001 192.168.56.15:7002 192.168.56.15:7003 192.168.56.15:7004 192.168.56.15:7005 --cluster-replicas 1 -a 123456

然后就是验证集群的高可用性,

可以用以下命令让7000端口的redis宕机,看其他redis节点还能不能正常访问,正常存值取值。

./redis-cli -h 192.168.56.15  -a 123456 -c -p 7000 shutdown

查看集群中所有节点的情况,

cluster nodes

刚开始6个节点都是启动状态,7000,7001,7002是从机,7003,7004,7005是主机

干掉了从机 7000

干掉主机7005

7002变成了主机

然后干掉7001

再干到7002

然后就不行了


springboot访问Redis集群

data-redis依赖啥的要有。然后在application.yml里配置redis相关

# Redis连接信息
#spring.data.redis.host=192.168.56.15
spring.data.redis.database=0
spring.data.redis.cluster.nodes=192.168.56.15:7000,192.168.56.15:7001,192.168.56.15:7002,192.168.56.15:7003,192.168.56.15:7004,192.168.56.15:7005
spring.data.redis.password=123456

# Redis连接池设置
spring.data.redis.pool.max-active=8 # 最大活动连接数
spring.data.redis.pool.max-idle=8 # 最大空闲连接数
spring.data.redis.pool.min-idle=0 # 最小空闲连接数
spring.data.redis.timeout=3000

单元测试

@SpringBootTest
public class RedisCluster {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Test
    void test(){
        stringRedisTemplate.opsForValue().set("redis_cluster","hello");
        System.out.println(stringRedisTemplate.opsForValue().get("redis_cluster"));
    }
}

然后把6个redis服务器中宕机几个,看看还能不能正常存数据取数据

分布式锁

普通业务减库存操作

@RestController
public class StockController {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock >0){
            stock--;
            stringRedisTemplate.opsForValue().set("stock",stock+"");
            System.out.println("扣减成功,剩余库存:"+stock);
        }else{
            System.out.println("扣减失败,库存不足");
        }

        return "end";
    }

}

存在超卖问题-单体项目解决

高并发场景下库存出现超卖情况,那么采用同步加锁的方式

  @RequestMapping("/deduct_stock")
    public String deductStock(){
        synchronized (this){
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock >0){
                stock--;
                stringRedisTemplate.opsForValue().set("stock",stock+"");
                System.out.println("扣减成功,剩余库存:"+stock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        }


        return "end";
    }

分布式集群项目解决

如果是分布式项目,集群项目的话,sync同步是JVM进程级别锁,在集群环境下,每一个tomcat就是一个JVM进程,此时将不起作用

使用redis实现分布式锁,setnx命令,springboot中使用setIfAbsent命令,返回boolean类型,如果是true则表示set值成功

在Redis中,`setnx`是一个命令,用于设置一个键值对,但只有在键不存在时才会设置成功。如果键已经存在,则不会进行任何操作。

具体来说,`setnx`命令的语法如下:

setnx key value

其中,`key`是要设置的键的名称,`value`是要设置的值。如果键不存在,则会将`key`和`value`关联起来,并返回1。如果键已经存在,则不会进行任何操作,并返回0。

`setnx`命令通常用于实现分布式锁。在使用`setnx`命令实现分布式锁时,多个客户端可以同时尝试获取锁,只有一个客户端能够获取到锁,并且在获取到锁的过程中,其他客户端无法获取到锁。这种方式可以确保在分布式系统中,只有一个客户端可以访问共享资源,从而避免了竞态条件和死锁等问题。

当第一个进程进来接口,先set一把锁,下一个进程进来后,执行同样的命令那么会返回false。就直接返回错误!但依然存在问题!

 @RequestMapping("/deduct_stock")
    public String deductStock(){
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "jf3q");
        if (!result) {
            return "error";
        }


        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock >0){
            stock--;
            stringRedisTemplate.opsForValue().set("stock",stock+"");
            System.out.println("扣减成功,剩余库存:"+stock);
        }else{
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }

解决第一个问题,当代码正常执行完了之后就要释放锁!但是还有问题!

@RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="product_001";
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "jf3q");
        if (!result) {
            return "error";
        }


        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock >0){
            stock--;
            stringRedisTemplate.opsForValue().set("stock",stock+"");
            System.out.println("扣减成功,剩余库存:"+stock);
        }else{
            System.out.println("扣减失败,库存不足");
        }
        //释放锁
        stringRedisTemplate.delete(lockKey);
        return "end";
    }

解决第二个问题,加锁后抛了异常造成锁释放的代码没有执行,就会出现死锁使用finally模块来释放锁。但还是有问题!

@RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="product_001";

        try {
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "jf3q");

            if (!result) {
                return "error";
            }
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock >0){
                stock--;
                stringRedisTemplate.opsForValue().set("stock",stock+"");
                System.out.println("扣减成功,剩余库存:"+stock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            //释放锁
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }
     

解决第三个问题,如果在执行减库存的业务逻辑时,服务器宕机了,或者kill-9杀了服务器进程,finally模块还是不会执行,依然会造成死锁,那么给这个锁加一个自动过期时间,比如10秒钟之后自动释放!但还是有问题!

解决第四个问题,如果在24,25行代码执行中间出现了异常,那么失效时间设置没有成功,则采用合并命令,让他们同时执行要不都成功,要不都失败!但还是有问题!

第5个问题,高并发场景下,可能会出现当前线程未执行完毕,但锁已经失效,然后当线程执行完了之后删除锁的时候是删除的下一个线程加的锁。

解决第五个问题,给锁的value值设置一个UUID随机数,删除锁之前判断一下,是自己的锁,才去删除!但还是有问题,如果业务没有执行完,锁就超时失效了,怎么办?

解决第六个问题,当主线程设置锁成功后,马上开启分线程,让分线程写定时任务去查询当前锁是否存在,如果存在的话,给他再蓄力10S过期失效时间,直到他被删除为止。就证明程序执行完毕。一般定时任务为过期时间的3分之1

最终解决方案:使用redisson客户端添加redisson依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.23.1</version>
        </dependency>

初始化redisson连接

 @Bean
    public Redisson redisson(){
        Config config=new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0).setPassword("123456");
        return (Redisson)Redisson.create(config);

    }

redisson客户端,lock方法上锁之后,其他的进程进来后会阻塞,不会往下继续执行,当主线程执行完了之后才会继续往下执行。并且会自动延迟锁的时间。

 @Autowired
    Redisson redisson;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="product_001";
        String clientId = UUID.randomUUID().toString();
        RLock redissonLock = redisson.getLock(lockKey);
        try {
//            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId,10, TimeUnit.SECONDS);
//            if (!result) {
//                return "error";
//            }

            redissonLock.lock(30,TimeUnit.SECONDS);
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if(stock >0){
                stock--;
                stringRedisTemplate.opsForValue().set("stock",stock+"");
                System.out.println("扣减成功,剩余库存:"+stock);
            }else{
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            redissonLock.unlock();
//            if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
//                //释放锁
//                stringRedisTemplate.delete(lockKey);
//            }

        }
        return "end";
    }

redisson逻辑结构图,如何提高分布式锁性能以及redisson架构中redis集群主节点宕机了,但锁还没来得及同步到从节点,从节点就升为主节点后没有锁了!(分段存储商品库存,redlock和zookeeper)


好博客就要一起分享哦!分享海报

此处可发布评论

评论(1展开评论

余生 能力:10

2024-01-08 19:20:04

2024一飞冲天
点击查看更多评论

展开评论

您可能感兴趣的博客

客服QQ 1913284695