安徽苏亚建设安装有限公司网站,树形菜单的网站代码,亚马逊商标备案是否必须做网站,做php网站的环境Spring Boot分布式锁的主要缺点包括但不限于以下几点#xff1a; 性能开销#xff1a;使用分布式锁通常涉及到网络通信#xff0c;这会引入额外的延迟和性能开销。例如#xff0c;当使用Redis或Zookeeper实现分布式锁时#xff0c;每次获取或释放锁都需要与这些服务进行交…
Spring Boot分布式锁的主要缺点包括但不限于以下几点 性能开销使用分布式锁通常涉及到网络通信这会引入额外的延迟和性能开销。例如当使用Redis或Zookeeper实现分布式锁时每次获取或释放锁都需要与这些服务进行交互。 单点故障风险如果依赖于某个特定的服务如Redis来管理锁那么该服务可能会成为单点故障。如果这个服务不可用所有依赖它的锁机制都会失效可能导致系统不稳定或者数据不一致的问题。 死锁风险在某些情况下如果没有正确处理异常情况或者客户端突然崩溃可能会导致死锁现象。例如如果一个持有锁的进程未能正确释放锁则其他等待该锁的进程将永远处于等待状态。 复杂性增加引入分布式锁增加了系统的复杂性。开发人员需要理解如何正确地使用锁并且要考虑到各种边界条件比如超时、重试逻辑等。此外还需要考虑不同类型的锁如公平锁、非公平锁以及它们对应用行为的影响。 资源竞争在高并发场景下多个实例尝试同时获取同一把锁会导致大量的资源竞争从而影响整体性能。特别是对于一些频繁读写的热点数据来说这种竞争可能会成为一个瓶颈。 实现差异不同的分布式锁实现之间存在差异这意味着迁移到另一种解决方案可能需要更改代码甚至重新设计架构。而且不是所有的实现都提供了相同的特性和保障。 租约管理和心跳检测一些分布式锁实现依赖于租约Lease和心跳来确保锁的有效性。这要求客户端定期向锁服务发送心跳信号以保持其持有的锁。如果网络分区发生或客户端出现故障可能会导致锁提前被释放进而引发数据一致性问题。 不适合长时间持有锁由于网络延迟和其他因素长时间持有分布式锁不是一个好的实践因为它可能会阻塞其他请求过久尤其是在高并发环境中。
Redis与Lua
使用Redis与Lua脚本结合的方式虽然有很多优点比如减少网络开销、提供原子性操作以及可复用等特性但也存在一些缺点 脚本大小和执行时间限制 Lua脚本的大小受到一定的限制过大的脚本可能无法成功加载到Redis中。Redis对Lua脚本的执行时间也有一定限制以防止单个脚本占用过多资源或导致服务器阻塞。如果脚本执行时间过长可能会触发客户端配置的时间限制进而中断脚本执行。 编写复杂度 编写Lua脚本需要一定的编程经验对于不熟悉Lua语言或者编程概念的开发者来说可能存在较高的学习曲线。如果Lua脚本逻辑复杂调试和维护也会变得更加困难。 阻塞风险 在Redis中Lua脚本是按照顺序串行执行的并且在执行期间会阻止其他命令的处理。因此长时间运行的脚本可能会造成Redis服务器的阻塞影响系统的响应速度和其他客户端的操作。不应该在Lua脚本中使用阻塞命令如BLPOP, BRPOP等因为这会导致Redis服务器在执行脚本时被阻塞无法处理其他请求。 错误处理机制有限 如果Lua脚本在执行过程中出现错误Redis不会回滚已经执行的部分这可能导致数据处于不一致状态。错误发生后通常只能通过日志来追踪问题所在缺乏更高级别的错误恢复机制。 内存消耗 Lua脚本一旦执行就会被缓存起来供后续调用使用这可以提高性能但同时也增加了内存使用量。如果脚本数量庞大或每个脚本占用较多内存可能会给Redis带来额外的压力。 版本兼容性 随着Redis版本的更新Lua解释器的版本也可能发生变化这可能会导致旧版本脚本在新版本Redis上不能正常工作的问题。 安全性考虑 使用Lua脚本时需要注意安全性避免恶意用户利用脚本执行攻击。例如应避免直接将用户输入作为脚本的一部分执行以防代码注入风险。
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.*;RestController
public class LuaController {Resourceprivate StringRedisTemplate stringRedisTemplate;private static final String LUA_SCRIPT if tonumber(redis.call(exists, KEYS[1])) 0 thenredis.call(set, KEYS[1],10)endif tonumber(redis.call(exists, KEYS[2])) 0 thenredis.call(sadd, KEYS[2],-1)endif tonumber(redis.call(get, KEYS[1])) 0 and tonumber(redis.call(sismember, KEYS[2] , ARGV[1])) 0 then redis.call(incrby, KEYS[1],-1) redis.call(sadd,KEYS[2],ARGV[1])return 1else return 0 end;GetMapping(/sk)public MapString,Object secKill(String pid){MapString,Object resp new HashMap();String uid String.valueOf(new Random().nextInt(100000000));ListString keys new ArrayList();keys.add(P pid); //P1010 String类型 用于保存产品库存量keys.add(U pid);//U1010 SET类型 用于保存秒杀确权的UIDDefaultRedisScriptLong redisScript new DefaultRedisScript(LUA_SCRIPT,Long.class);Long result stringRedisTemplate.execute(redisScript, keys,uid);resp.put(uid, uid);resp.put(result, result);return resp;}
}Spring Retry Redis Watch实现乐观锁
Spring Retry 和 Redis 的 WATCH 命令可以结合使用来实现乐观锁尤其是在处理分布式环境下的并发控制时。这种组合可以有效地减少锁的开销并提供一种非阻塞的方式来处理并发更新。
实现步骤 使用 WATCH 监视键 在开始事务之前使用 WATCH 命令监视一个或多个键。这告诉Redis在这些键上设置一个“观察点”如果这些键在事务执行过程中被其他客户端修改则当前事务将失败。 发起 MULTI 开始事务 当所有需要监视的键都已确定后使用 MULTI 命令开启一个事务。从这一刻起所有后续命令都会被收集起来直到 EXEC 被调用。 尝试执行命令 在事务中执行所需的命令例如 GET、SET 等最后通过 EXEC 提交事务。如果自 WATCH 以来没有键被修改那么事务将成功提交否则EXEC 将返回 null 表示事务失败。 使用 Spring Retry 进行重试 如果由于其他客户端修改了受监视的键而导致事务失败可以通过 Spring Retry 来自动重试整个过程。这样应用程序可以在不增加复杂性的情况下处理并发冲突。 定义重试逻辑 需要为 Spring Retry 配置适当的重试策略包括最大重试次数、等待间隔等参数。同时应该考虑何时停止重试比如当达到最大重试次数或者超过某个时间限制时。
添加依赖
dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.23.5/version/dependency--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/dependencydependencygroupIdorg.springframework.retry/groupIdartifactIdspring-retry/artifactIdversion2.0.0/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency/dependencies 业务逻辑
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Objects;Service
public class SampleService {Resourceprivate RedisTemplateString,Object redisTemplate;Retryable(retryFor IllegalStateException.class, maxAttempts 2)Transactionalpublic String saWatch(){System.out.println(executing sa());ListObject execute redisTemplate.execute(new SessionCallback() {public ListObject execute(RedisOperations operations) throws DataAccessException {redisTemplate.watch(sa001);redisTemplate.multi();redisTemplate.opsForValue().set(pri001, -100);try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}redisTemplate.opsForValue().set(sa001, 100);return redisTemplate.exec();}});if(Objects.isNull(execute)){System.out.println(发现并发冲突 execute);throw new IllegalStateException(Retry);}else{System.out.println(exec执行成功 execute);}return success;}
}redisTemplate.execute(SessionCallback): 使用 SessionCallback 来定义一个Redis会话其中包含了一系列命令这些命令将在一个单独的事务中执行。 redisTemplate.watch(sa001): 开始监视键 sa001确保在接下来的事务期间如果该键被其他客户端修改则当前事务将失败。 redisTemplate.multi(): 启动一个Redis事务之后的所有命令都会被收集起来直到调用 exec()。
控制器
package com.cokerlk.redisclientside;import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;RestController
public class SampleController {Resourceprivate RedisTemplateString,Object redisTemplate;Resourceprivate SampleService sampleService;GetMapping(/test)public String testWatch(){sampleService.saWatch();return success;}GetMapping(/setSA)public String setSA(){redisTemplate.opsForValue().set(sa001,300);return success;}}Application
package com.cokerlk.redisclientside;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;SpringBootApplication
EnableRetry
public class RedisClientSideApplication {public static void main(String[] args) {SpringApplication.run(RedisClientSideApplication.class, args);}}测试
###
GET http://localhost:8080/test###
GET http://localhost:8080/setSAexecuting sa()
exec执行成功[true, true]
executing sa()
exec执行成功[]