Redis工作开发中真实使用场景

RedisTemplate的使用
@Autowired
private RedisTemplate<String,String> redisTemplate;
//简单的键值对存储到redis中
    //判断是否存在这个key
    redisTemplate.hasKey("")
    //获取redis中的这个key
    redisTemplate.opsForValue().get("");
    //设置redis的key和值
    redisTemplate.opsForValue().set("key",token,1, TimeUnit.DAYS);
Redis的使用场景

1、在实现短信登录的时候需要限制每个用户发送短信的数量,例如每天只能发送三次短信,这个限制的实现可以通过redis来实现,把手机号码或者用户id作为key,当天发送短信的次数作为值,并且设置过期时间,短信验证码也是可以用redis实现的,把手机号码或者用户id作为key,验证码作为值,同样需要设置过期时间

2、在调用第三方接口,比如某个消息提醒的接口的时候需要发请求获取一个token,然后再调用接口的时候带上这个token才可以有权限访问,这个token一般是有一天有效期的,所以这个token可以放在Redis里面,设置一天过期,这样可以避免每个接口调用的时候都去获取一个新的token,从而消耗了资源【需要注意一个地方,那就是在测试环境和生成环境之间切换发送请求调用第三方接口获取token的时候,会导致原来的token直接失效,例如我测试环境开vpn,然后调用了这个接口生成了一个token存储在测试环境的redis里面,然后由于代码更新到了生成环境上去,所以我去生成环境测试的时候也调用这个接口,那么就会重新生成一个新的token存在生产环境的redis里面,这个时候测试环境中redis存储的token就是失效的token,但是由于有效期设置的一天,所以当又切换会测试环境调用这个接口的时候就会返回一个旧的失效的token,那么拿着这个失效的token去调用其他接口就会导致调用失败,但是这个旧的失效的token会一直在redis中保留一天才会过期,那么这个期间就不会重新获取新的token,因为我代码的逻辑是判断这个key是否有值,有值的话就直接返回redis中这个key对应的值,没有才会去重新获取token,所以就会存在这个问题,那么解决办法就是在拿着失效的token去发请求调用第三方接口的时候,会响应token失效的标识,那么判断这个标识进行处理,处理逻辑就是删掉redis中旧的token,重新获取新的token】

package com.misoft.util;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.redxun.common.base.entity.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/**
 * 第三方服务消息推送工具类
 */
@Component
public class ThirdPartyMsgUtils {


    @Value("${third.party.clientId}")
    private String clientId;

    @Value("${third.party.clientSecret}")
    private String clientSecret;

    @Value("${third.party.baseUrl}")
    private String baseUrl;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 发送消息通知
     * @param param 消息参数
     * @return 处理结果
     */
    public JsonResult sendMsg(JSONObject param) {
        String accessToken = null;
        // 获取接口调用凭证【优先从缓存获取】
        if (!redisTemplate.hasKey("THIRD_PARTY_ACCESS_TOKEN")) {
            HashMap<String, Object> tokenParams = new HashMap<>();
            tokenParams.put("grant_type", "client_credentials");
            tokenParams.put("client_id", clientId);
            tokenParams.put("client_secret", clientSecret);
            
            HttpResponse tokenResp = HttpRequest
                    .post(baseUrl + "/oauth/token")
                    .form(tokenParams)
                    .timeout(20000)
                    .execute();
            
            if (tokenResp.getStatus() == 200) {
                String tokenJson = tokenResp.body();
                JSONObject tokenObj = JSON.parseObject(tokenJson);
                accessToken = tokenObj.getString("access_token");
                
                if (org.springframework.util.StringUtils.isEmpty(accessToken)) {
                    return JsonResult.Fail("认证失败");
                }
                // 缓存凭证,有效期5小时
                redisTemplate.opsForValue().set("THIRD_PARTY_ACCESS_TOKEN", accessToken, 5, TimeUnit.HOURS);
            }
        } else {
            accessToken = redisTemplate.opsForValue().get("THIRD_PARTY_ACCESS_TOKEN");
        }

        // 发送消息请求
        HttpResponse msgResp = HttpRequest
                .post(baseUrl + "/api/message/send")
                .header("token", "true")
                .header("Authorization", "Bearer " + accessToken)
                .body(param.toJSONString(), "application/json;charset=UTF-8")
                .timeout(20000)
                .execute();

        if (msgResp.getStatus() == 200) {
            return JsonResult.Success("消息发送成功");
        } else {
            // 处理凭证无效的情况
            if (msgResp.body().contains("invalid_token")) {
                redisTemplate.delete("THIRD_PARTY_ACCESS_TOKEN");
                String retryKey = "THIRD_PARTY_RETRY_COUNT";
                String retryCount = redisTemplate.opsForValue().get(retryKey);
                
                if (org.springframework.util.StringUtils.isEmpty(retryCount)) {
                    redisTemplate.opsForValue().set(retryKey, "1", 5, TimeUnit.MINUTES);
                    return sendMsg(param); // 重试一次
                } else {
                    return JsonResult.Fail("消息发送失败:凭证无效");
                }
            }
        }
        return JsonResult.Fail("消息发送失败");
    }
}

3、存储移动端签到码的有效期,因为移动端的缓存都是本地缓存,只有在自己的手机上才有用,所以这里需要在后端去调用redis接口来存储签到码的有效期

<think>首先,用户在问的是在SpringBoot项目开发使用synchronized的关键场景和最佳实践。用户引用了几个站内引用,我需要参考这些引用来提供回答。从引用中提取相关信息:-引用[1]:讨论了SpringBoot在高并发场景下的性能提升,提到了缓存组件EhCache和Redis,Tomcat线程池配置,服务器硬件资源,负载均衡,高可用部署。但没有直接提到synchronized。-引用[2]:关于监听器在SpringBoot中的使用,提到监听Servlet上下文对象用于初始化数据和缓存。没有提到synchronized。-引用[3]:涉及使用Netty开发WebSocket服务在SpringBoot中,提到了多线程。这可能与synchronized相关,但具体未讨论使用场景。-引用[4]:直接讨论了Spring事务管理下synchronized锁失效问题,提到在@Transactional方法中使用synchronized可能导致数据库更新不一致,给出了一个例子:@TransactionalpublicsynchronizedStringgenerateNewCode(),在高并发下转账金额不对。所以,引用[4]是最相关的,它强调在SpringBoot中使用synchronized时可能遇到的问题,特别是在事务管理上下文中的锁失效。用户的问题:synchronized在SpringBoot开发中的使用场景及最佳实践。系统指令:-所有行内数学表达式必须使用$...$格式,但用户问题中没有数学内容,所以可能不需要。-独立公式必须使用$$...$$格式并单独成段,如果涉及数学时。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识,例如[^1]。-回答结构清晰,帮助用户逐步解决问题,保证真实可靠。我的回答结构:1.**介绍**:简述synchronized的作用和它在SpringBoot环境中的重要性。2.**使用场景**:列出在SpringBoot中常见的使用synchronized的关键场景。3.**最佳实践**:讨论如何正确使用synchronized,避免常见问题。4.**注意事项和潜在问题**:基于引用[4],解释在Spring事务中使用synchronized可能导致的问题。5.**解决方案和替代方案**:建议最佳实践和可选技术。6.**结尾**:生成相关问题。7.**引用标识**:自然地添加引用到相关部分。关键使用场景(在SpringBoot上下文中):-**共享资源访问**:例如,当多个线程需要访问共享数据时,如全局缓存、单例Bean的属性。-**代码块同步**:在并发操作中,确保原子性操作,如生成唯一ID、计数器。-**防止竞态条件**:在服务方法中避免数据不一致。-**基于引用[4]的事务问题**:在Spring的事务管理下,使用synchronized可能不足以保证原子性,因为事务提交发生在方法调用后。最佳实践:1.**避免在事务方法中使用synchronized**:如引用[4]所示,synchronized只在同一个JVM中有效,Spring事务涉及数据库,可能导致锁失效。最佳实践是使用数据库级别的锁或乐观锁。2.**最小化锁定范围**:使用synchronized块而不是整个方法,锁定共享资源。3.**利用Spring的并发工具**:使用@Scope("prototype")为Bean创建多实例,或使用java.util.concurrent包中的工具如ReentrantLock。4.**使用synchronizedonvolatile字段**:如果必须使用,确保字段volatile以避免可见性问题。5.**在无状态服务中谨慎使用**:如果服务Bean是无状态的,可能不需要synchronized。6.**测试和监控**:在高并发场景下测试,使用工具监控线程行为。从引用[4],具体的教训:-@Transactional和synchronized结合时,事务提交在方法执行后,不同线程可能同时执行方法,导致数据不一致。-最佳实践是使用数据库锁如SELECTFORUPDATE或乐观锁如@VersioninJPA。回答时用中文。生成相关问题:如系统指令所示,类似于示例中的"相关问题"部分。生成3-5个相关的问题。结构:-**引入问题**-**使用场景**-**最佳实践**-**注意事项**-**解决方案**-**相关问题**添加引用:当提到引用[4]时,在段落末尾加[^4]。现在,草拟回答大纲:1.**介绍**:SpringBoot项目开发中,synchronized用于实现线程同步,但需注意在事务环境和分布式系统中的限制。2.**关键使用场景**:-共享资源访问,如全局变量或Bean属性。-原子操作,如生成唯一序列号。-引用监听器或WebSocket处理。3.**最佳实践**:-优先使用数据库锁或Spring的并发机制。-如果使用synchronized,确保在单一JVM中,并使用最小范围。-避免在@Transactional方法中结合synchronized(参考[^4])。4.**潜在问题与解决方案**:-问题:事务中的锁失效(如引用[^4])。-解决方案:使用@Lock注解、乐观锁、或外部分布式锁。5.**结论和建议**。6.**相关问题**。在数学表达式中:如果没有数学,不需添加。但如果提及计数器或性能分析,可能用到。暂时假设不涉及复杂数学。最终回答用中文。</think> ### Spring Boot 开发中 synchronized 的使用场景及最佳实践 在 Spring Boot 项目开发中,`synchronized` 关键字用于实现线程同步,确保共享资源在多线程环境下的安全访问,尤其在高并发场景下处理竞态条件(race conditions)时尤为关键。然而,Spring Boot 的依赖注入和事务管理机制(如 `@Transactional`)可能引入额外复杂性,导致 `synchronized` 失效或性能下降(如引用[4]所示:在高并发转账操作中,`@Transactional` 与 `synchronized` 结合使用时,数据库数据出现不一致)。下面我将逐步分析关键使用场景、最佳实践、注意事项以及解决方案,帮助你安全高效地使用 `synchronized`。本回答基于 Spring Boot 的标准机制和常见并发模式。 --- ### 一、synchronized 在 Spring Boot 中的关键使用场景 在 Spring Boot 环境中,`synchronized` 主要用于保护共享资源的原子性和一致性,尤其是在单实例 JVM(单一应用部署)场景下。以下是常见的应用场景: 1. **共享 Bean 属性同步**: - 当多个线程访问 Spring Bean 的共享状态(如全局计数器、缓存变量)时,需使用 `synchronized` 确保原子操作。 - 示例:一个单例(Singleton)Bean 的计数器属性,用于统计 API 调用次数。 ```java @Service public class CounterService { private int count = 0; // 共享属性 // 使用 synchronized 方法确保递增操作的原子性 public synchronized void increment() { count++; } } ``` - 在这种场景下,`synchronized` 能防止多个线程同时操作导致的数据不一致[^4]。 2. **生成唯一 ID 或序列号**: - 当业务需要生成全局唯一序列(如订单号、UUID)时,`synchronized` 可确保操作序列化。 - 示例:一个服务类中的方法,生成不重复的代码(如引用[4]中的 `generateNewCode()` 方法)。 ```java @Service public class CodeGenerator { // 生成新代码,使用 synchronized 块锁定临界区 public synchronized String generateNewCode() { // 复杂生成逻辑,确保每次返回唯一值 return "code-" + System.currentTimeMillis(); } } ``` - 这在无外部分布式锁的情况下,适用于低并发需求。 3. **监控与监听器初始化**: - 在 Spring Boot 的监听器(如 Servlet 上下文监听器)中,初始化和更新共享缓存时,需同步操作以数据安全(如引用[2]中提到的缓存初始化)。 ```java @WebListener public class CacheInitializer implements ServletContextListener { private static volatile Map<String, Object> cache; // 共享缓存 @Override public void contextInitialized(ServletContextEvent sce) { // 使用 synchronized 块初始化缓存,避免多个事件并发执行 synchronized (this) { if (cache == null) { cache = new ConcurrentHashMap<>(); // 初始化数据 } } } } ``` - 这在应用启动时常见,防止并发初始化导致的缓存冲突。 4. **WebSocket 或 Netty 处理并发消息**: - 在使用 WebSocket(如引用[3]中的 Netty 集成)处理实时消息时,消息处理逻辑可能需要同步访问共享状态。 ```java @Component @ServerEndpoint("/ws") public class WebSocketHandler { private static final Object lock = new Object(); // 锁对象 @OnMessage public void handleMessage(String message) { // 使用 synchronized 块保护消息处理逻辑 synchronized (lock) { // 更新全局状态,如用户连接计数 } } } ``` - 在实时通信场景下,确保数据一致性。 这些场景都强调 `synchronized` 在本地 JVM 中的有效性,但当 Spring Boot 涉及事务、缓存或分布式部署时,需额外小心。 --- ### 二、synchronized 的最佳实践 为了避免锁失效或性能瓶颈(如引用[4]中的问题),在 Spring Boot 中使用 `synchronized` 时应遵循以下最佳实践。目标是最小化锁定范围、提升并发效率,并结合 Spring 机制。 1. **优先替代方案,而非依赖 synchronized**: - **使用数据库锁或乐观锁**:在事务方法中,`synchronized` 容易失效(因为它只锁定 JVM 级线程),推荐用 Spring Data JPA 的 `@Lock` 或数据库锁(如 SQL 的 `SELECT FOR UPDATE`)。例如,修复引用[4]的问题: ```java @Repository public interface AccountRepository extends JpaRepository<Account, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) // 使用悲观锁 Account findByAccountNumber(String accountNumber); } @Service public class TransferService { @Autowired private AccountRepository repository; @Transactional public void transfer(String from, String to, int amount) { Account accFrom = repository.findByAccountNumber(from); Account accTo = repository.findByAccountNumber(to); // 执行原子转账操作,避免 synchronized } } ``` - **利用 java.util.concurrent 包**:如使用 `ReentrantLock` 或 `AtomicInteger` 提供更细粒度的控制。例如: ```java @Service public class CounterService { private final Lock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } } ``` 2. **最小化锁定范围**: - 避免锁整个方法(如 `public synchronized void...`),改用 `synchronized` 块锁定特定对象。 ```java public void increment() { // 仅锁定共享变量,减少阻塞时间 synchronized (this) { count++; } } ``` - 使用 `volatile` 关键字:确保变量的可见性,避免指令重排序,适合低争用场景。例如:`private volatile int counter;`。 3. **避免结合 @Transactional**: - 在事务方法中使用 `synchronized` 时,事务提交可能在方法执行后发生(Spring 的代理机制),导致数据库锁不一致(如引用[4]问题)。最佳实践是: - 将事务注解移至外层方法,内层逻辑用 synchronized。 - 或完全移除此组合:如果涉及数据库操作,优先用事务管理器的隔离级别(如 `@Transactional(isolation = Isolation.SERIALIZABLE)`)。 ```java @Service public class CodeService { // 无事务的方法,内部同步生成代码 public synchronized String generateCode() { // 代码生成逻辑 return "unique-code"; } // 外层事务方法调用 @Transactional public void processCode() { String code = generateCode(); // 事务操作 } } ``` 4. **优化并发性能**: - **Bean Scope 管理**:如果服务 Bean 是单例(默认),确保共享状态同步;但如果是原型(prototype),`@Scope("prototype")` 可以减少锁争用。 ```java @Service @Scope("prototype") // 每个请求新实例 public class StatelessService { // 无共享状态,可能不需 synchronized } ``` - **结合缓存机制**:在高并发下,用缓存(如 EhCache 或 Redis,如引用[1])减少数据库访问,降低锁需求。 - **线程池配置**:在 Spring Boot 中优化 Tomcat 线程池(如 `server.tomcat.max-threads`),避免过多线程阻塞在 synchronized 上(参考引用[1])。 5. **测试和监控**: - 使用工具如 JUnit、Jmeter 或 Spring Actuator 测试并发行为(如引用[4]中的 Jmeter 测试)。 - 监控指标:在 Prometheus 或 Log4j 中跟踪锁竞争时间(lock contention),早期发现问题。 --- ### 三、注意事项与常见问题 - **锁失效风险**:在分布式或容器化部署中,多个实例共享数据时,`synchronized` 无效,需用分布式锁(如 Redis Redlock)[^1]。 - **性能陷阱**:过度使用 synchronized 可能导致线程饥饿或死锁(deadlock)。基准测试显示,在高并发下,不必要锁会增加延迟。 - **推荐实践总结**:只在本地 JVM 且低争用场景使用 synchronized;否则,结合 Spring 的并发工具或数据库机制。 --- ### 四、生成相关问题 以下问题扩展了 Spring Boot 并发编程的讨论,帮助你深入理解: 1. 在 Spring Boot 中,如何避免使用 synchronized 时的死锁问题? 2. Spring Boot 分布式系统下有哪些替代 synchronized 的方案? 3. 如何使用 Spring AOP 优化线程同步机制? 4. Spring Boot 事务管理下的乐观锁是如何工作的?[^4] 如果需要更多代码示例或特定场景讨论,请随时提问!作为参考,推荐学习 Java Concurrency in Practice 和 Spring Boot 官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好运仔dzl

打赏有用的话还要工作干嘛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值