प्रौद्योगिकी साझेदारी

SpringBoot Redis इत्यस्य वास्तविकप्रयोगपरिदृश्यानां च संचालनाय Redisson इत्यस्य उपयोगं करोति

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

प्रस्तावना

अस्तिSpringBoot इत्यनेन Redis इत्यस्य संचालनार्थं RedisTemplate तथा StringRedisTemplate इत्येतयोः उपयोगः भवति, वयं RedisTemplate इति परिचयं कृतवन्तः तथा च SpringBoot RedisTemplate तथा StringRedisTemplate इत्येतयोः माध्यमेन Redis इत्यस्य संचालनं कथं करोति इति ।
RedisTemplate的好处就是基于SpringBoot自动装配的原理,使得整合redis时比较简单。

अतः यतः SrpingBoot RedisTemplate इत्यस्य माध्यमेन Redis इत्यस्य संचालनं कर्तुं शक्नोति, तस्मात् Redisson पुनः किमर्थं दृश्यते?Rddisson चीनी दस्तावेजीकरण
Reddissin也是一个redis客户端,其在提供了redis基本操作的同时,还具备其他客户端一些不具备的高精功能,例如:分布式锁+看门狗、分布式限流、远程调用等等。Reddissin的缺点是api抽象,学习成本高。

I. अवलोकनम्

spring-boot 2.x संस्करणात् आरभ्य, spring-boot-data-redis पूर्वनिर्धारितरूपेण आँकडानां संचालनार्थं Lettuce client इत्यस्य उपयोगं करोति ।

१.१ सलादः

SpringBoot2之后,默认就采用了lettuce。
एकः उन्नतः Redis क्लायन्ट् अस्ति, एकः घटना-सञ्चालितः संचार-स्तरः यः थ्रेड्-सुरक्षित-समन्वयनस्य, अतुल्यकालिक-प्रतिक्रियाशील-उपयोगस्य कृते, क्लस्टर-सेन्टिनेल्, पाइपलाइन्-एन्कोडर-इत्येतयोः समर्थनार्थं नेट्टी-रूपरेखायाः आधारेण अस्ति
Lettuce इत्यस्य API थ्रेड्-सुरक्षितम् अस्ति तथा च विभिन्नानि ऑपरेशन्स् पूर्णं कर्तुं एकं Lettuce संयोजनं संचालितुं शक्नोति संयोजनदृष्टान्तं (StatefulRedisConnection) बहुषु थ्रेड् मध्ये समवर्तीरूपेण अभिगन्तुं शक्यते ।

१.२ रेडिसन्

नेट्टी-रूपरेखायाः आधारेण एकः घटना-सञ्चालितः संचार-स्तरः, विधिः अतुल्यकालिकः अस्ति, एपिआइ थ्रेड्-सुरक्षितः अस्ति, तथा च विविधानि कार्याणि पूर्णं कर्तुं एकं रेडिसन-संयोजनं संचालितुं शक्नोति
एतत् वितरितं स्केल-करणीयं च जावा-दत्तांशसंरचनां कार्यान्वितं करोति, स्ट्रिंग्-सञ्चालनस्य समर्थनं न करोति, तथा च क्रमाङ्कनम्, लेनदेनं, पाइपलाइन्, विभाजनं च इत्यादीनां Redis-विशेषतानां समर्थनं न करोति
अनेकाः वितरिताः सम्बद्धाः संचालनसेवाः प्रदाति, यथा वितरिताः तालाः, वितरितसङ्ग्रहाः, तथा च Redis मार्गेण विलम्बपङ्क्तयः समर्थयितुं शक्नोति ।

सारांशं कुरुत: सलादस्य उपयोगं प्राथमिकताम् अददात्, यस्य कृते वितरित-तालाः वितरित-सङ्ग्रहाः इत्यादीनां उन्नत-वितरित-विशेषतानां आवश्यकता भवति ।

2. Spring-Boot इत्यनेन Redisson इत्यस्य एकीकरणं भवति

२.१ आश्रितानां परिचयः

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.6</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

सूचना: एतस्य आश्रयस्य प्रवर्तनानन्तरं पुनः प्रवर्तनस्य आवश्यकता नास्तिspring-boot-starter-data-redis,तत्‌redisson-spring-boot-starter अस्य आन्तरिकरूपेण प्रवर्तनं कृतम् अस्ति तथा च रेडिस् इत्यस्य लुटुस् तथा जेडिस् ग्राहकाः अपवर्जिताः सन्ति । अतः application.yaml इत्यस्मिन् Luttuce तथा Jedis इत्येतयोः विन्यासः प्रभावी न भविष्यति ।
अत्र चित्रविवरणं सम्मिलितं कुर्वन्तु

परियोजनायां Redisson इत्यस्य उपयोगं कुर्वन्तः वयं सामान्यतया RedissonClient इत्यस्य उपयोगं कुर्मः तथापि, केचन मित्राणि RedissonClient इत्यस्य संचालनार्थं असुविधाजनकं मन्यन्ते, अथवा कार्याणां कृते RedisTemplate इत्यस्य उपयोगं कर्तुं प्राधान्यं ददति RedisTemplate श्रेणी।refer toSpringBoot इत्यनेन Redis इत्यस्य संचालनार्थं RedisTemplate तथा StringRedisTemplate इत्येतयोः उपयोगः भवति

परियोजनायाः रेडिसॉन् इत्यस्य परिचयस्य अनन्तरं रेडिसटेम्पलेट् इत्यस्य अधः प्रयुक्तः संयोजनकारखानः अपि रेडिसन् इति ज्ञातम् ।
अत्र चित्रविवरणं सम्मिलितं कुर्वन्तु

२.२ विन्याससञ्चिका

application.yaml मध्ये redis विन्याससूचना योजयन्तु ।

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

२.३ विन्यासवर्गः

@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]);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

२.४ कथं प्रयोगः करणीयः

@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
        }
	}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

२.५ व्यावहारिकपरिदृश्यानि

२.५.१ वितरितं तालम्

किञ्चित् अनुभवं विद्यमानाः छात्राः वितरित-तालानां उपयोगस्य उल्लेखं कुर्वन्तः redis इति चिन्तयन्ति अतः redis वितरित-तालानां कथं कार्यान्वयनम् करोति?

वितरित-तालानां अत्यावश्यकं लक्ष्यं रेडिस्-नगरे एकं गर्तं धारयितुं भवति (सरलतया वक्तुं शक्यते यत्, यदा अन्याः प्रक्रियाः अपि गर्तस्य कब्जां कर्तुम् इच्छन्ति तदा ते पश्यन्ति यत् गर्ते पूर्वमेव बृहत् मूलीः सन्ति , भवता त्यक्तव्यं वा पश्चात् पुनः प्रयासः वा कर्तव्यः।

वितरिततालानां कृते सामान्यतया प्रयुक्ताः पद्धतयः

1. setNx आदेशस्य उपयोगं कुर्वन्तु
अस्य आदेशस्य विस्तृतं वर्णनं (यदि नास्ति तर्हि सेट् भवति यदि निर्दिष्टं कुञ्जी नास्ति तर्हि तत् सेट् भविष्यति (व्यापारनिष्पादनस्य समाप्तेः अनन्तरं कीलं विलोपयितुं del आदेशं आह्वयन्तु) गर्तम्) । उदाहरणतया:

# set 锁名 值
setnx distribution-lock  locked

// dosoming

del  distribution-lock
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

परन्तु अस्मिन् आदेशे समस्या अस्ति यदि निष्पादनतर्कस्य समस्या अस्ति तर्हि del निर्देशः निष्पादितः न भवेत्, तथा च तालः गतिरोधः भविष्यति ।
कदाचित् केचन मित्राणि विचारपूर्वकं चिन्तितवन्तः यत् वयम् अस्य कीलस्य कृते अन्यं समाप्तिसमयं निर्धारयितुं शक्नुमः। उदाहरणतया:

setnx distribution-lock  locked

expire distribution-lock  10

// dosoming

del  distribution-batch
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

एतत् कृत्वा अपि तर्कस्य समस्याः सन्ति यतः setnx तथा expire इति द्वौ आदेशौ स्तः, यदि redis सर्वरः setnx तथा expire इत्येतयोः मध्ये लम्बते तर्हि expire न निष्पादितं भविष्यति, तथा च expiration time सेटिंग् विफलं भविष्यति, तथा च lock भविष्यति अद्यापि सेट् भवति।

मूलकारणं यत् setnx, expire इति आदेशद्वयं परमाणुदेशः नास्ति ।

तथा redis वस्तूनि setnx तथा expire इत्यस्य समस्यां समाधातुं न शक्नुवन्ति, यतः expire setnx इत्यस्य execution result इत्यस्य उपरि निर्भरं भवति यदि setnx सफलं न भवति तर्हि expire निष्पादनीयम् । अन्यथा चेत् विषयाणां न्यायः कर्तुं न शक्यते, अतः वितरित-तालानां कार्यान्वयनार्थं setnx+expire-विधिः इष्टतमं समाधानं नास्ति ।

2. setNx Ex आदेशस्य उपयोगं कुर्वन्तु
setNx+expire इत्यस्य समस्यायाः उपरि उल्लेखः कृतः अस्ति यत् एतस्याः समस्यायाः समाधानार्थं Redis अधिकारिणः संस्करणे 2.8 मध्ये set आदेशस्य विस्तारितानि मापदण्डानि प्रवर्तयन्ति, येन setnx तथा expire आदेशाः एकत्र निष्पादयितुं शक्यन्ते उदाहरणतया:

# set 锁名 值 ex 过期时间(单位:秒) nx
set distribution-lock locked ex 5 nx

// doSomthing

del distribution-lock
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

तार्किकरूपेण setNx Ex पूर्वमेव इष्टतमं समाधानम् अस्ति तथा च वितरितं तालं गतिरोधं न जनयिष्यति ।

परन्तु अस्माकं विकासकाले अद्यापि समस्याः उत्पद्यन्ते किमर्थम्।
यतः वयं आरम्भे अस्य तालस्य कृते अवधिसमाप्तिसमयं सेट् कुर्मः, यदि अस्माकं व्यावसायिकतर्कस्य निष्पादनसमयः निर्धारितसमाप्तिसमयं अतिक्रमति तर्हि किम्? एतादृशी स्थितिः भविष्यति यत्र एकः सूत्रः निष्पादनं न सम्पन्नवान् तथा च द्वितीयः सूत्रः वितरितं तालं धारयितुं शक्नोति ।
अतः यदि भवान् setNx Ex संयोजनस्य उपयोगं करोति तर्हि भवता सुनिश्चितं कर्तव्यं यत् भवतः तालस्य समयसमाप्तिः तालस्य अनन्तरं व्यावसायिकनिष्पादनसमयात् अधिकः अस्ति ।

3. lua स्क्रिप्ट + watch dog स्वचालितविस्तारतन्त्रस्य उपयोगं कुर्वन्तु
अस्य समाधानस्य बहुभागः अहं अन्तर्जालद्वारा प्राप्तुं शक्नोमि, अतः अहम् अत्र विस्तरेण न गमिष्यामि ।

रेडिसनः वितरितानि तालानि कार्यान्वयति

उपरि प्रवर्तिताः setNx तथा setNx Ex आदेशाः Redis सर्वरेण प्रदत्ताः देशीयाः आदेशाः सन्ति, तथा च setNx Ex आदेशस्य व्यावसायिकतर्कः lock timeout इत्यस्मात् अधिकः इति समस्यायाः समाधानार्थं Redisson आन्तरिकं प्रदाति तालस्य निरीक्षणार्थं एकः वॉचडॉग् स्थापितः भवति तस्य कार्यं रेडिसन-दृष्टान्तस्य बन्दीकरणात् पूर्वं तालस्य वैधतायाः अवधिं निरन्तरं विस्तारयितुं भवति । पूर्वनिर्धारितरूपेण, watchdog check lock timeout 30 सेकण्ड् भवति (अर्थात् नवीकरणं 30 सेकण्ड् भवति Config.lockWatchdogTimeout परिवर्तनं कृत्वा अपि पृथक् निर्दिष्टुं शक्यते lock इत्यस्य प्रारम्भिकः समाप्तिसमयः अपि पूर्वनिर्धारितरूपेण 30 सेकण्ड् भवति

// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
@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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

२.५.२ वर्तमानसीमाकरणम्

वयं उच्चसमवर्ततायाः अन्तर्गतं अन्तरफलकानाम् अथवा व्यावसायिकतर्कस्य वर्तमानप्रवाहं सीमितुं आवश्यकतायाः समस्यायाः सामनां कुर्मः वस्तुतः, Redisssion इत्यस्य अपि एतादृशं वर्तमानं सीमितं कार्यम् अस्ति ।

RateLimiter इति टोकन बाल्टी करण्ट् लीमिटिङ्ग् इति कथ्यते एषः प्रकारः करण्ट् लिमिटिङ्ग् प्रथमं टोकन बाल्टीं परिभाषितुं, निश्चितकालस्य अन्तः कियन्तः टोकन्स् उत्पद्यन्ते इति निर्दिष्टुं, प्रत्येकं टोकन बाल्टीतः निर्दिष्टसङ्ख्यां प्राप्तुं च भवति .यदि भवान् If successful इति प्राप्नोति तर्हि वैधप्रवेशरूपेण सेट् भवति ।