java脚手架系列10-统一缓存、分布式锁

之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master

目录

  • [1 缓存](#1 缓存)
  • [2 分布式锁](#2 分布式锁)
    • [2.1 分布式锁的作用](#2.1 分布式锁的作用)
    • [2.2 分布式锁的实现方式](#2.2 分布式锁的实现方式)
    • [2.3 代码实践](#2.3 代码实践)

1 缓存

我们知道,无论是数据库或者是文件系统,大部分都存在于磁盘之上,而磁盘往往是整个访问链路中速度最慢之一,因此如何快速避开磁盘访问,往往是业务需要解决性能问题之一,因此缓存就是为了这里而生。在数据库或者对象存储中,其实它们本身就是说过了缓存,而这里要将的缓存是基于业务层面的。在业务中,某些热点数据由于其访问量巨大,因此可以放入缓存中实现。下面就以redis为例,做一个redis集成到项目的脚手架:

参考common-redis子模块和manage-biz子模块

1)在common子模块下面新建common-redis子模块,该子模块的作用就是配置redis基本配置,以spring.factories方式发布

2)创建RedisConfig配置类,里面默认配置redisTemplate和stringRedisTemplate(同时使用@ConditionalOnMissingBean({RedisTemplate.class})注解,使得引用该子模块也可以自定义自己的Template)

注意:redis有2种不同的template(2种的key不能共享)

1.StringRedisTemplate:以String作为存储方式:默认使用StringRedisTemplate,其value都是以String方式存储

2.RedisTemplate:

1)使用默认RedisTemplate时,其value都是根据jdk序列化的方式存储

2)自定义Jackson2JsonRedisSerializer序列化,以json格式存储,其key与StringRedisTemplate共享,返回值是LinkedHashMap(本案例中使用该种方式)

3)自定义GenericJackson2JsonRedisSerializer序列化,以json格式存储,其key与StringRedisTemplate共享,返回值是原先对象(因为保存了classname)

java 复制代码
package com.demo.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    /**
     * 主要做redis配置。redis有2种不同的template(2种的key不能共享)
     * 1.StringRedisTemplate:以String作为存储方式:默认使用StringRedisTemplate,其value都是以String方式存储
     * 2.RedisTemplate:
     *    1)使用默认RedisTemplate时,其value都是根据jdk序列化的方式存储
     *    2)自定义Jackson2JsonRedisSerializer序列化,以json格式存储,其key与StringRedisTemplate共享,返回值是LinkedHashMap
     *    3)自定义GenericJackson2JsonRedisSerializer序列化,以json格式存储,其key与StringRedisTemplate共享,返回值是原先对象(因为保存了classname)
     */
    @Bean
    @ConditionalOnMissingBean({RedisTemplate.class})
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(factory);
        //本实例采用Jackson2JsonRedisSerializer
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    @ConditionalOnMissingBean({StringRedisTemplate.class})
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        return template;
    }
}

3)在spring.factories配置RedisConfig类

4)在manage-biz子模块中引入common-redis

xml 复制代码
<dependency>
    <groupId>org.example</groupId>
    <artifactId>common-redis</artifactId>
    <version>${project.version}</version>
</dependency>

5)在yaml配置文件中redis访问(由于manage-biz的yaml配置在nacos上面,因此需要nacos修改cloud-manage-biz-service文件配置)

yaml 复制代码
spring:
	# redis配置
	redis:
	  database: 1
	  host: 127.0.0.1
	  port: 6379
	  password:
	  timeout: 30000
	  client-type: jedis
	  jedis:
	    pool:
	      max-active: 1000
	      max-idle: 100
	      min-idle: 0
	      maxWait: 1000

6)编写TestRedisController和TestRedisService演示redis存储对象和byte字节示例

2 分布式锁

2.1 分布式锁的作用

在数据库,我们对一个资源进行操作,比如更新一行数据,那么数据库根据你设置的事务级别,一般都会对其加锁,加锁的原因其实就是怕并发操作时,避免脏数据。

而在不同服务之间,其锁的概念也是有的。比如我们为了避免前端重复点击,一般会给前端返回一个key,然后前端提交数据时,将key返回给后端,后端验证是否同时有同一个key多个请求,如果存在则返回重复操作。

2.2 分布式锁的实现方式

常见的分布式锁实现有以下几种方式:

1)基于数据库实现分布式锁

2)基于zookeeper实现分布式锁

3)基于redis实现分布式锁

从理解的难易程度角度(从低到高) :数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高):Zookeeper >= 缓存 > 数据库

从性能角度(从高到低):缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低):Zookeeper > 缓存 > 数据库

2.3 代码实践

参考子模块:common-redis和distributed-lock-service

本案例中使用redis来实现分布式锁,同时引入redisson框架(该框架封装了基于redis的分布式锁,让我们非常方便使用。另外如zookeeper也有Curator框架)

1)新建distributed-lock-service子模块,引入以下依赖:

xml 复制代码
<dependency>
    <groupId>org.example</groupId>
    <artifactId>common-redis</artifactId>
    <version>${project.version}</version>
</dependency>
<!--redisson中已经引入spring-starter-web,因此无需在引入 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.12.5</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2)编写RedissonConfig,配置RedissonClient

java 复制代码
package com.demo.redis.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String redisPassword;

    @Bean
    public RedissonClient getRedisson(){

        Config config = new Config();
        //单机模式  依次设置redis地址和密码
        config.useSingleServer().
                setAddress("redis://" + host + ":" + port);
//                setPassword(redisPassword);
        return Redisson.create(config);
    }
}

3)编写RedisLockController,实现一个扣取库存的分布式锁模拟场景

4)通过启动2台服务器(记得修改接口),然后分别访问2台服务器的/redisLock/exportInventory接口,查看扣取库存日志是否正确

相关推荐
腥臭腐朽的日子熠熠生辉17 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian19 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之24 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码44 分钟前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
浩浩kids2 小时前
Hadoop•踩过的SHIT
大数据·hadoop·分布式
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存