分布式微服务业务的并发完善

level1 单机服务

最开始先假设为单机服务,该服务的功能是售卖某件商品。那么controller层的大概业务可以这么写:

@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	
	@GetMapping("/buy_goods")
	public String buy_Goods() {
		String result = template.opsForValue().get("goods:001");
		int goodsNumber = result == null ? 0 : Integer.parseInt(result);
		if (goodsNumber > 0) {
			int realNumber = goodsNumber - 1;
			template.opsForValue().set("goods:001", String.valueOf(realNumber));
			System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
		}
		System.out.println("商品已经售完~ 服务端口: " + serverPort);
        return "商品已经售完~ 服务端口: " + serverPort;
	}
}

这个是最初的版本,会出现单机超卖的情况,考虑加锁来进行改进

level2 单机加锁版

@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	
	@GetMapping("/buy_goods")
	public synchronized String buy_Goods() {
		String result = template.opsForValue().get("goods:001");
		int goodsNumber = result == null ? 0 : Integer.parseInt(result);
		if (goodsNumber > 0) {
			int realNumber = goodsNumber - 1;
			template.opsForValue().set("goods:001", String.valueOf(realNumber));
			System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
		}
		System.out.println("商品已经售完~ 服务端口: " + serverPort);
        return "商品已经售完~ 服务端口: " + serverPort;
	}

level3 分布式微服务版1.0

假设现在服务由原先的1台变成多台,那么就需要进行分布式部署。在这种情况下单机锁只能够锁住本机,但是无法锁住其他的服务,因此在分布式的情况下,单机锁失效了,此时应该将锁加在redis上,即使用redis分布式锁。

@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	private String REDIS_LOCK = "REDIS_LOCK";
	
	@GetMapping("/buy_goods")
	public String buy_Goods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		Boolean flag = template.setIfAbsent(REDIS_LOCK, value);
		if (Boolean.FALSE.equals(flag)){
			//没有抢到锁
			return "此次没有抢到锁~";
		}
		try{
			String result = template.opsForValue().get("goods:001");
			int goodsNumber = result == null ? 0 : Integer.parseInt(result);
			if (goodsNumber > 0) {
				int realNumber = goodsNumber - 1;
				template.opsForValue().set("goods:001", String.valueOf(realNumber));
				System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
	            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
			}
			System.out.println("商品已经售完~ 服务端口: " + serverPort);
	        return "商品已经售完~ 服务端口: " + serverPort;
		}
		finally{
			template.delete(REDIS_LOCK);
		}
	}

level4 分布式微服务版2.0

在1.0版本中,可以发现一个问题:如果在运行该服务时,服务主机宕机了,那么Redis锁就无法删除,其他主机的相同服务会一直等待,导致该业务出现异常。因此可以替锁加上一个过期时间,来保证在主机出现问题时,也能够正常运行。这里假设Redis锁保存10秒就自动删除。

@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	private String REDIS_LOCK = "REDIS_LOCK";
	
	@GetMapping("/buy_goods")
	public String buy_Goods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		Boolean flag = template.setIfAbsent(REDIS_LOCK, value, 10L, TimteUnit.SECONDS);
		if (Boolean.FALSE.equals(flag)){
			//没有抢到锁
			return "此次没有抢到锁~";
		}
		try{
			String result = template.opsForValue().get("goods:001");
			int goodsNumber = result == null ? 0 : Integer.parseInt(result);
			if (goodsNumber > 0) {
				int realNumber = goodsNumber - 1;
				template.opsForValue().set("goods:001", String.valueOf(realNumber));
				System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
	            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
			}
			System.out.println("商品已经售完~ 服务端口: " + serverPort);
	        return "商品已经售完~ 服务端口: " + serverPort;
		}
		finally{
			template.delete(REDIS_LOCK);
		}
	}

level5 分布式微服务版3.0

2.0版中解决了主机宕机,redis锁无法删除的情况,引入了过期时间,但这又会产生一个新的问题:假设服务A在执行逻辑时超过了锁自动删除的时间,此时其他服务就可以申请锁了,这里假设服务B申请了锁,而服务A最终会删除锁,此时删除的是服务B的锁,这就乱套了,即服务只能删除自己的锁,不能删除其他服务的锁。

@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	private String REDIS_LOCK = "REDIS_LOCK";
	
	@GetMapping("/buy_goods")
	public String buy_Goods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		Boolean flag = template.setIfAbsent(REDIS_LOCK, value, 10L, TimteUnit.SECONDS);
		if (Boolean.FALSE.equals(flag)){
			//没有抢到锁
			return "此次没有抢到锁~";
		}
		try{
			String result = template.opsForValue().get("goods:001");
			int goodsNumber = result == null ? 0 : Integer.parseInt(result);
			if (goodsNumber > 0) {
				int realNumber = goodsNumber - 1;
				template.opsForValue().set("goods:001", String.valueOf(realNumber));
				System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
	            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
			}
			System.out.println("商品已经售完~ 服务端口: " + serverPort);
	        return "商品已经售完~ 服务端口: " + serverPort;
		}
		finally{
			if (template.opsForValue().get(REDIS_LOCK).equals(value)){
				template.delete(REDIS_LOCK);
			}
		}
	}

level5 分布式微服务版4.0

3.0版本保证了服务只删除自己加的锁,但是这一过程不是原子性的,此时有两种方法来解决:

  • 使用LUA脚本来实现删除锁这一过程
  • 使用事务来实现删除锁这一过程。
@RestController
public class GoodController {
	@Autowired
    private StringRedisTemplate template;
	@Value("${server.port}")
	private String serverPort;
	private String REDIS_LOCK = "REDIS_LOCK";
	
	@GetMapping("/buy_goods")
	public String buy_Goods() {
		String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
		Boolean flag = template.setIfAbsent(REDIS_LOCK, value, 10L, TimteUnit.SECONDS);
		if (Boolean.FALSE.equals(flag)){
			//没有抢到锁
			return "此次没有抢到锁~";
		}
		try{
			String result = template.opsForValue().get("goods:001");
			int goodsNumber = result == null ? 0 : Integer.parseInt(result);
			if (goodsNumber > 0) {
				int realNumber = goodsNumber - 1;
				template.opsForValue().set("goods:001", String.valueOf(realNumber));
				System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
	            return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
			}
			System.out.println("商品已经售完~ 服务端口: " + serverPort);
	        return "商品已经售完~ 服务端口: " + serverPort;
		}
		finally{
			//使用事务来保证原子性
			while (true) {
				template.watch(REDIS_LOCK);
				if (template.opsForValue().get(REDIS_LOCK).equals(value)) {
					template.setEnableTransactionSupport(true);
					template.multi();
					template.delete(REDIS_LOCK);
					List<Object> list = template.exec();
					if (list == null) continue;
				}
					template.unwatch();
					break;
			}
			//使用lua脚本来保证原子性
			Jedis jedis = RedisUtils.getJedis();
			String script = "if redis.call('get', KEYS[1]) == ARGV[1]" +
							"then " +
							"return redis.call('del', KEYS[1]) " + 
							"else " + 
							"return 0 " + 
							"end"; 
			try{
				Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
				if ("1".equals(o)){
					System.out.println("删锁成功");
				}
				else {
					System.out.println("删锁失败");
				}
			}
			finally{
				if (jedis != null)
					jedis.close();
			}
		}
	}

level5 分布式微服务版4.0

以上分布式版本都存在一个问题:如何保证业务的执行时间小于锁的过期时间?
并且,假设redis现在也是集群环境,会出现一种情况:在master进行了数据的修改,在还未异步同步到slave节点时,master宕机了,slave节点升级为master节点,此时会出现数据不一致的问题。
为了解决以上问题,使用redisson来解决分布式锁问题。

@RestController
public class GoodController {
    private static final String REDIS_LOCK = "LOCK";

    @Autowired
    private StringRedisTemplate template;

    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private Redisson redisson;
    
    @GetMapping("/buy_goods")
    public String buy_Goods() {
        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        RLock lock = redisson.getLock(REDIS_LOCK);
        lock.lock();
        try {
            String result = template.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                template.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
                return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
            }
            System.out.println("商品已经售完~ 服务端口: " + serverPort);
            return "商品已经售完~ 服务端口: " + serverPort;
        } finally {
            lock.unlock();
        }
    }
}

level6 分布式微服务final版

使用redisson基本上解决了分布式下的并发需求,但是有可能会出现异常:IllegalMonitorStateException:attempt to unlock lock, not locked by current thread by node id: xxxx。即想要解锁,但发现不是由当前线程加的锁。可以在unlock处进行进一步的改进。

@RestController
public class GoodController {
    private static final String REDIS_LOCK = "LOCK";

    @Autowired
    private StringRedisTemplate template;

    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private Redisson redisson;
    
    @GetMapping("/buy_goods")
    public String buy_Goods() {
        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
        RLock lock = redisson.getLock(REDIS_LOCK);
        lock.lock();
        try {
            String result = template.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodsNumber > 0) {
                int realNumber = goodsNumber - 1;
                template.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort);
                return "成功买到商品,库存还剩下: " + realNumber + "件, 服务端口: " + serverPort;
            }
            System.out.println("商品已经售完~ 服务端口: " + serverPort);
            return "商品已经售完~ 服务端口: " + serverPort;
        } finally {
            if (lock.isLocked()){
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值