Heim  >  Artikel  >  Datenbank  >  So implementieren Sie verteilte Sperren mit Redis

So implementieren Sie verteilte Sperren mit Redis

尚
Original
2019-07-05 16:45:209322Durchsuche

So implementieren Sie verteilte Sperren mit Redis

Verwenden Sie Redis, um verteilte Sperren zu implementieren

Einführung in Redis-Funktionen

1. Unterstützt umfangreiche Datentypen wie String, List, Map, Set, ZSet usw.

2. Unterstützt Datenpersistenz, RDB- und AOF-Methoden

3. Unterstützt den Cluster-Arbeitsmodus, starke Partitionsfehlertoleranz

4 >

5. Unterstützen Sie Transaktionen

6. Unterstützen Sie Veröffentlichungen und Abonnements

Redis implementiert verteilte Sperren mit dem SETNX-Befehl:

SETNX-Schlüsselwert

Setzen Sie den Wert von key genau dann auf value, wenn der Schlüssel nicht vorhanden ist.

Wenn der angegebene Schlüssel bereits vorhanden ist, ergreift SETNX keine Aktion.

SETNX ist die Abkürzung für „SET if Not eXists“ (wenn es nicht existiert, dann SET).

Verfügbare Versionen: >= 1.0.0 Zeitkomplexität: O(1) Rückgabewert:

Erfolgreich festgelegt, Rückgabe 1 .

Festlegen fehlgeschlagen, Rückgabe 0.

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 设置成功
(integer) 1

redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0

redis> GET job                   # 没有被覆盖
"programmer"

Zuerst müssen wir eine öffentliche Redis-Zugriffstoolklasse kapseln. Diese Klasse muss eine RedisTemplate-Instanz und eine ValueOperations-Instanz einfügen. Die ValueOperations-Instanz wird verwendet, da die von Redis implementierte verteilte Sperre den einfachsten String-Typ verwendet. Darüber hinaus müssen wir drei Methoden kapseln, nämlich setIfObsent (String-Schlüssel, String-Wert), Expire (String-Schlüssel, langes Timeout, TimeUnit-Einheit) und delete (String-Schlüssel), die den Befehlen SETNX, Expire und Del entsprechen von Redis bzw. Das Folgende ist die spezifische Implementierung der Redis-Zugriffstoolklasse:

@Component
public class RedisDao {

	@Autowired
	private RedisTemplate redisTemplate;
	
	@Resource(name="redisTemplate")
	private ValueOperations<Object, Object> valOpsObj;
	
	/**
	 * 如果key不存在,就存储一个key-value,相当于SETNX命令
	 * @param key      键
	 * @param value    值,可以为空
	 * @return
	 */
	public boolean setIfObsent (String key, String value) {
		return valOpsObj.setIfAbsent(key, value);
	}
	
	/**
	 * 为key设置失效时间
	 * @param key       键
	 * @param timeout   时间大小
	 * @param unit      时间单位
	 */
	public boolean expire (String key, long timeout, TimeUnit unit) {
		return redisTemplate.expire(key, timeout, unit);
	}
	
	/**
	 * 删除key
	 * @param key 键
	 */
	public void delete (String key) {
		redisTemplate.delete(key);
	}
}

hat die Implementierung der Redis-Zugriffstoolklasse abgeschlossen. Nun muss überlegt werden, wie der Wettbewerb um verteilte Sperren simuliert werden kann. Da Redis selbst verteilte Cluster unterstützt, muss es nur Geschäftsszenarien mit Multithread-Verarbeitung simulieren. Hier wird der Thread-Pool zur Simulation verwendet:

@RestController
@RequestMapping("test")
public class TestController {

	private static final Logger LOG = LoggerFactory.getLogger(TestController.class);  //日志对象
	@Autowired
	private RedisDao redisDao;
	//定义的分布式锁key
	private static final String LOCK_KEY = "MyTestLock";
	
	@RequestMapping(value={"testRedisLock"}, method=RequestMethod.GET)
	public void testRedisLock () {
		ExecutorService executorService = Executors.newFixedThreadPool(5);
		for (int i = 0; i < 5; i++) {
			executorService.submit(new Runnable() {
				@Override
				public void run() {
				    //获取分布式锁
					boolean flag = redisDao.setIfObsent(LOCK_KEY, "lock");
					if (flag) {
						LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁成功");
						//获取锁成功后设置失效时间
						redisDao.expire(LOCK_KEY, 2, TimeUnit.SECONDS);
						try {
							LOG.info(Thread.currentThread().getName() + ":处理业务开始");
							Thread.sleep(1000); //睡眠1000ms模拟处理业务
							LOG.info(Thread.currentThread().getName() + ":处理业务结束");
							//处理业务完成后删除锁
							redisDao.delete(LOCK_KEY);
						} catch (InterruptedException e) {
							LOG.error("处理业务异常:", e);
						}
					} else {
						LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁失败");
					}
				}
			});
		}
	}
}

Durch den obigen Code können die folgenden Fragen auftreten:

Wenn der Thread das nicht erhält Verteilte Sperre. Warum nicht versuchen, die Sperre erneut zu erhalten?

Nachdem der Thread die verteilte Sperre erfolgreich erworben hat, legt er die Ablaufzeit der Sperre fest.

Warum müssen Sie die Sperre löschen, nachdem die Thread-Geschäftsverarbeitung abgeschlossen ist?

Diese Fragen können wir besprechen.

Erstens führt der SETNX-Befehl von Redis keine Operation aus, wenn der Schlüssel bereits vorhanden ist, sodass die von SETNX implementierte verteilte Sperre keine Wiedereintrittssperre ist. Natürlich können Sie es auch n-mal über den Code wiederholen oder bis die verteilte Sperre erhalten wird. Dies garantiert jedoch keinen fairen Wettbewerb und ein Thread wird blockiert, weil er auf die Sperre gewartet hat. Daher eignet sich die von Redis implementierte verteilte Sperre besser für Szenarien, in denen gemeinsam genutzte Ressourcen einmal geschrieben und mehrmals gelesen werden.

Zweitens muss die verteilte Sperre eine Ablaufzeit festlegen, und die Ablaufzeit muss größer sein als die für die Geschäftsverarbeitung erforderliche Zeit (um die Datenkonsistenz sicherzustellen). Daher sollte während der Testphase die für die normale Geschäftsabwicklung erforderliche Zeit so genau wie möglich vorhergesagt und die Ausfallzeit festgelegt werden, um einen Deadlock aus bestimmten Gründen im Geschäftsabwicklungsprozess zu verhindern.

Drittens muss nach Abschluss der Geschäftsabwicklung die Sperre gelöscht werden.

Das Festlegen der verteilten Sperre und das Festlegen der Ablaufzeit für die Sperre erfolgen oben in zwei Schritten. Eine vernünftigere Möglichkeit sollte darin bestehen, die verteilte Sperre und die Ablaufzeit für die Sperre in einem Vorgang festzulegen. Entweder gelingt es beiden, oder sie scheitern beide. Der Implementierungscode lautet wie folgt:

/**
* Redis访问工具类
*/
@Component
public class RedisDao {

	private static Logger logger = LoggerFactory.getLogger(RedisDao.class);
	
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	
	/**
	 * 设置分布式锁    
	 * @param key     键
	 * @param value   值
	 * @param timeout 失效时间
	 * @return
	 */
	public boolean setDistributeLock (String key, String value, long timeout) {
		RedisConnection connection = null;
		boolean flag = false;
		try {
			//获取一个连接
			connection = stringRedisTemplate.getConnectionFactory().getConnection();
			//设置分布式锁的同时为锁设置失效时间
			connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.SET_IF_ABSENT);
			flag = true;
		} catch (Exception e) {
			logger.error("set automic lock error:", e);
		} finally {
			//使用后关闭连接
			connection.close();
		}
		return flag;
	}
	
	/**
	 * 查询key的失效时间
	 * @param key       键
	 * @param timeUnit  时间单位
	 * @return
	 */
	public long ttl (String key, TimeUnit timeUnit) {
		return stringRedisTemplate.getExpire(key, timeUnit);
	}
}

/**
* 单元测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo1ApplicationTests {

	private static final Logger LOG = LoggerFactory.getLogger(Demo1ApplicationTests.class);
		
	@Autowired
	private RedisDao redisDao;
	
	@Test
	public void testDistributeLock () {
		String key = "MyDistributeLock";
		//设置分布式锁,失效时间20s
		boolean result = redisDao.setDistributeLock(key, "1", 20);
		if (result) {
			LOG.info("设置分布式锁成功");
			long ttl = redisDao.ttl(key, TimeUnit.SECONDS);
			LOG.info("{}距离失效还有{}s", key, ttl);
		}
	}
}

Führen Sie die Unit-Test-Klasse aus und die Ergebnisse lauten wie folgt:

2019-05-15 13:07:10.827 - 设置分布式锁成功
2019-05-15 13:07:10.838 - MyDistributeLock距离失效还有19s

Weitere Informationen zu Redis finden Sie im

Tutorial zur Redis-Nutzung Kolumne!

Das obige ist der detaillierte Inhalt vonSo implementieren Sie verteilte Sperren mit Redis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn