τα στοιχεία επικοινωνίας μου
Ταχυδρομείο[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
υπάρχειΤο SpringBoot χρησιμοποιεί το RedisTemplate και το StringRedisTemplate για τη λειτουργία του Redis, παρουσιάσαμε το RedisTemplate και τον τρόπο με τον οποίο το SpringBoot λειτουργεί το Redis μέσω του RedisTemplate και του StringRedisTemplate.
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。
Αφού λοιπόν το SrpingBoot μπορεί να χειριστεί το Redis μέσω του RedisTemplate, γιατί το Redisson εμφανίζεται ξανά;Κινεζική τεκμηρίωση Rddisson
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。
Ξεκινώντας από την έκδοση Spring-boot 2.x, το spring-boot-data-redis χρησιμοποιεί το πρόγραμμα-πελάτη Lettuce για τη λειτουργία των δεδομένων από προεπιλογή.
SpringBoot2之后,默认就采用了lettuce。
Είναι ένας προηγμένος πελάτης Redis, ένα επίπεδο επικοινωνίας που βασίζεται σε συμβάντα που βασίζεται στο πλαίσιο Netty για συγχρονισμό ασφαλούς νήματος, ασύγχρονη και αντιδραστική χρήση, υποστηρικτικά συμπλέγματα, Sentinel, αγωγούς και κωδικοποιητές.
Το API του Lettuce είναι ασφαλές ως προς το νήμα και μπορεί να λειτουργήσει μία μόνο σύνδεση Lettuce για την ολοκλήρωση διαφόρων λειτουργιών.
Ένα επίπεδο επικοινωνίας που βασίζεται σε συμβάντα που βασίζεται στο πλαίσιο Netty, η μέθοδος είναι ασύγχρονη, το API είναι ασφαλές για νήματα και μπορεί να λειτουργήσει μία μόνο σύνδεση Redisson για να ολοκληρώσει διάφορες λειτουργίες.
Υλοποιεί μια κατανεμημένη και κλιμακούμενη δομή δεδομένων Java, δεν υποστηρίζει λειτουργίες συμβολοσειρών και δεν υποστηρίζει λειτουργίες Redis όπως ταξινόμηση, συναλλαγές, αγωγούς και κατατμήσεις.
Παρέχει πολλές κατανεμημένες σχετικές υπηρεσίες λειτουργίας, όπως κατανεμημένες κλειδαριές, κατανεμημένες συλλογές και μπορεί να υποστηρίξει ουρές καθυστέρησης μέσω του Redis.
Συνοψίζω: Δώστε προτεραιότητα χρησιμοποιώντας το Μαρούλι, το οποίο απαιτεί προηγμένες κατανεμημένες λειτουργίες όπως κατανεμημένες κλειδαριές και κατανεμημένες συλλογές Προσθήκη Redisson για συνδυασμό με αυτό.
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
Ειδοποίηση: Μετά την εισαγωγή αυτής της εξάρτησης, δεν χρειάζεται να την εισαγάγετε ξανά
spring-boot-starter-data-redis
,Οτιredisson-spring-boot-starter
Έχει εισαχθεί εσωτερικά και αποκλείει τους πελάτες της Redis' Luttuce και Jedis. Επομένως, η διαμόρφωση των Luttuce και Jedis στο application.yaml δεν θα τεθεί σε ισχύ.
Όταν χρησιμοποιούμε το Redisson σε ένα έργο, γενικά χρησιμοποιούμε το RedissonClient για λειτουργίες δεδομένων Κατηγορία RedisTemplate.αναφέρομαι σεΤο SpringBoot χρησιμοποιεί το RedisTemplate και το StringRedisTemplate για τη λειτουργία του Redis。
Διαπιστώθηκε ότι μετά την εισαγωγή του Redisson από το έργο, το εργοστάσιο σύνδεσης που χρησιμοποιήθηκε στο κάτω μέρος του RedisTemplate ήταν επίσης το Redisson.
Προσθέστε πληροφορίες διαμόρφωσης redis στο application.yaml.
spring:
data:
redis:
mode: master
# 地址
host: 30.46.34.190
# 端口,默认为6379
port: 6379
# 密码,没有不填
password: ''
# 几号库
database: 1
sentinel:
master: master
nodes: 30.46.34.190
cluster:
nodes: 30.46.34.190
lettuce:
pool:
# 连接池的最大数据库连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 50
# 连接池中的最小空闲连接
min-idle: 8
@Configuration
@EnableConfigurationProperties({RedisProperties.class})
public class RedissonConfig {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
@Value("${spring.data.redis.mode}")
private String redisMode;
private final RedisProperties redisProperties;
public RedissonConfig(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
/**
* 逻辑参考 RedissonAutoConfiguration#redisson()
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson(@Autowired(required = false) List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers) throws IOException {
Config config = new Config();
config.setCheckLockSyncedSlaves(false);
int max = redisProperties.getLettuce().getPool().getMaxActive();
int min = redisProperties.getLettuce().getPool().getMinIdle();
switch (redisMode) {
case "master": {
SingleServerConfig singleConfig = config.useSingleServer()
.setAddress(REDIS_PROTOCOL_PREFIX + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
singleConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
singleConfig.setConnectionPoolSize(max);
singleConfig.setConnectionMinimumIdleSize(min);
}
break;
case "sentinel": {
String[] nodes = convert(redisProperties.getSentinel().getNodes());
SentinelServersConfig sentinelConfig = config.useSentinelServers()
.setMasterName(redisProperties.getSentinel().getMaster())
.addSentinelAddress(nodes)
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
sentinelConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
sentinelConfig.setMasterConnectionPoolSize(max);
sentinelConfig.setMasterConnectionMinimumIdleSize(min);
sentinelConfig.setSlaveConnectionPoolSize(max);
sentinelConfig.setSlaveConnectionMinimumIdleSize(min);
}
break;
case "cluster": {
String[] nodes = convert(redisProperties.getCluster().getNodes());
ClusterServersConfig clusterConfig = config.useClusterServers()
.addNodeAddress(nodes)
.setPassword(redisProperties.getPassword());
if (redisProperties.getConnectTimeout() != null) {
clusterConfig.setConnectTimeout((int) redisProperties.getConnectTimeout().toMillis());
}
clusterConfig.setMasterConnectionMinimumIdleSize(min);
clusterConfig.setMasterConnectionPoolSize(max);
clusterConfig.setSlaveConnectionMinimumIdleSize(min);
clusterConfig.setSlaveConnectionPoolSize(max);
}
break;
default:
throw new IllegalArgumentException("无效的redis mode配置");
}
if (redissonAutoConfigurationCustomizers != null) {
for (RedissonAutoConfigurationCustomizer customizer : redissonAutoConfigurationCustomizers) {
customizer.customize(config);
}
}
return Redisson.create(config);
}
private String[] convert(List<String> nodesObject) {
List<String> nodes = new ArrayList<String>(nodesObject.size());
for (String node : nodesObject) {
if (!node.startsWith(REDIS_PROTOCOL_PREFIX)) {
nodes.add(REDIS_PROTOCOL_PREFIX + node);
} else {
nodes.add(node);
}
}
return nodes.toArray(new String[0]);
}
}
@Component
public class RedissonService {
@Resource
protected RedissonClient redissonClient;
public void redissonExists(String key){
RBucket<String> rBucketValue = redissonClient.getBucket(key, StringCodec.INSTANCE);
if (rBucketValue.isExists()){
String value = rBucketValue.get();
// doSomething
} else {
// doElseSomething
}
}
}
Οι μαθητές με κάποια εμπειρία σκέφτονται το redis όταν αναφέρουν τη χρήση κατανεμημένων κλειδαριών Λοιπόν, πώς το redis εφαρμόζει τις κατανεμημένες κλειδαριές;
Ο βασικός στόχος των κατανεμημένων κλειδαριών είναι να καταλάβουν ένα λάκκο στο Redis (με απλά λόγια, είναι η αρχή ότι τα καρότα καταλαμβάνουν λάκκους, όταν και άλλες διαδικασίες θέλουν να καταλάβουν το λάκκο, διαπιστώνουν ότι υπάρχουν ήδη μεγάλα ραπανάκια). πρέπει να τα παρατήσεις ή να προσπαθήσεις ξανά αργότερα.
1. Χρησιμοποιήστε την εντολή setNx
Η λεπτομερής περιγραφή αυτής της εντολής είναι (ρυθμίστε εάν δεν υπάρχει, θα τεθεί με επιτυχία (κατάληψη του λάκκου αφού ολοκληρωθεί η εκτέλεση, καλέστε την εντολή del για να διαγράψετε το κλειδί). ο λάκκος). για παράδειγμα:
# set 锁名 值
setnx distribution-lock locked
// dosoming
del distribution-lock
Αλλά υπάρχει πρόβλημα με αυτήν την εντολή Εάν υπάρχει πρόβλημα στη λογική εκτέλεσης, η εντολή del μπορεί να μην εκτελεστεί και το κλείδωμα θα γίνει αδιέξοδο.
Ίσως κάποιοι φίλοι να έχουν σκεφτεί ότι μπορούμε να ορίσουμε άλλο χρόνο λήξης για αυτό το κλειδί. για παράδειγμα:
setnx distribution-lock locked
expire distribution-lock 10
// dosoming
del distribution-batch
Ακόμη και αφού γίνει αυτό, εξακολουθούν να υπάρχουν προβλήματα με τη λογική, καθώς το setnx και το expire είναι δύο εντολές, εάν ο διακομιστής redis κλείνει μεταξύ του setnx και του expire, το expire δεν θα εκτελεστεί και η ρύθμιση του χρόνου λήξης θα αποτύχει και το κλείδωμα θα γίνει. θα γίνει αδιέξοδο.
Η βασική αιτία είναι ότι οι δύο εντολές setnx και expire δεν είναι ατομικές εντολές.
Και τα πράγματα redis δεν μπορούν να λύσουν το πρόβλημα του setnx και λήγουν, επειδή το expire εξαρτάται από το αποτέλεσμα εκτέλεσης του setnx Εάν το setnx δεν πετύχει, το expire δεν πρέπει να εκτελεστεί. Τα πράγματα δεν μπορούν να κριθούν διαφορετικά, επομένως η μέθοδος setnx+expire για την υλοποίηση κατανεμημένων κλειδαριών δεν είναι η βέλτιστη λύση.
2. Χρησιμοποιήστε την εντολή setNx Ex
Το πρόβλημα του setNx+expire αναφέρθηκε παραπάνω Προκειμένου να λυθεί αυτό το πρόβλημα, οι υπεύθυνοι της Redis εισήγαγαν τις εκτεταμένες παραμέτρους της εντολής set στην έκδοση 2.8, έτσι ώστε οι εντολές setnx και expire να μπορούν να εκτελεστούν μαζί. για παράδειγμα:
# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx
// doSomthing
del distribution-lock
Λογικά μιλώντας, το setNx Ex είναι ήδη μια βέλτιστη λύση και δεν θα προκαλέσει το κατανεμημένο κλείδωμα να γίνει αδιέξοδο.
Αλλά μπορεί να προκύψουν προβλήματα κατά τη διάρκεια της ανάπτυξής μας Γιατί;
Εφόσον ορίσαμε έναν χρόνο λήξης για αυτό το κλείδωμα στην αρχή, τι γίνεται αν ο χρόνος εκτέλεσης της επιχειρηματικής μας λογικής υπερβαίνει τον καθορισμένο χρόνο λήξης; Θα υπάρξει μια κατάσταση όπου ένα νήμα δεν έχει ολοκληρώσει την εκτέλεση και το δεύτερο νήμα μπορεί να συγκρατεί το κατανεμημένο κλείδωμα.
Επομένως, εάν χρησιμοποιείτε τον συνδυασμό setNx Ex, πρέπει να βεβαιωθείτε ότι το χρονικό όριο λήξης της κλειδαριάς σας είναι μεγαλύτερο από το χρόνο εκτέλεσης της επιχείρησης μετά το κλείδωμα.
3. Χρησιμοποιήστε το μηχανισμό αυτόματης επέκτασης lua script + watch dog
Μπορώ να βρω πολλές από αυτήν τη λύση στο διαδίκτυο, επομένως δεν θα μπω σε λεπτομέρειες εδώ.
Οι εντολές setNx και setNx Ex που εισήχθησαν παραπάνω είναι εγγενείς εντολές που παρέχονται από τον διακομιστή Redis και υπάρχουν περισσότερα ή λιγότερα προβλήματα Προκειμένου να λυθεί το πρόβλημα ότι η επιχειρηματική λογική της εντολής setNx Ex είναι μεγαλύτερη από το χρονικό όριο κλειδώματος, η Redisson παρέχει εσωτερική. Έχει εγκατασταθεί ένας φύλακας για την παρακολούθηση της κλειδαριάς. Η λειτουργία του είναι να παρατείνει συνεχώς την περίοδο ισχύος της κλειδαριάς πριν κλείσει η παρουσία του Redisson. Από προεπιλογή, το χρονικό όριο κλειδώματος του φύλακα είναι 30 δευτερόλεπτα (δηλαδή, η ανανέωση είναι 30 δευτερόλεπτα, τροποποιώντας το Config.lockWatchdogTimeout. Ο αρχικός χρόνος λήξης του κλειδώματος είναι επίσης 30 δευτερόλεπτα).
// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
@Resource
RedissonClient redissonClient;
@GetMapping("/testDistributionLock")
public BaseResponse<String> testRedission(){
RLock lock = redissonClient.getLock("redis:distributionLock");
try {
boolean locked = lock.tryLock(10, 3, TimeUnit.SECONDS);
if(locked){
log.info("获取锁成功");
Thread.sleep(100);
return ResultUtils.success("ok" );
}else{
log.error("获取锁失败");
return ResultUtils.error(ErrorCode.SYSTEM_ERROR);
}
} catch (InterruptedException e) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"出异常了");
} finally {
lock.unlock();
}
}
Αντιμετωπίζουμε το πρόβλημα της ανάγκης περιορισμού της τρέχουσας ροής διεπαφών ή επιχειρηματικής λογικής υπό υψηλή ταυτόχρονη χρήση.
Το RateLimiter ονομάζεται περιορισμός ρεύματος κουβαδιού Αυτός ο τύπος περιορισμού ρεύματος είναι να ορίσει πρώτα έναν κάδο διακριτικών, να καθορίσει πόσα διακριτικά θα δημιουργηθούν μέσα σε μια συγκεκριμένη χρονική περίοδο και να λάβει τον καθορισμένο αριθμό διακριτικών από τον κάδο διακριτικών κάθε φορά που γίνεται πρόσβαση σε αυτόν. Εάν αποκτήσετε Εάν είναι επιτυχής, ορίζεται ως έγκυρη πρόσβαση.