真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何用redis來(lái)實(shí)現(xiàn)分布式鎖

本篇內(nèi)容主要講解“如何用redis來(lái)實(shí)現(xiàn)分布式鎖”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“如何用redis來(lái)實(shí)現(xiàn)分布式鎖”吧!

創(chuàng)新互聯(lián)建站從2013年創(chuàng)立,先為吉安等服務(wù)建站,吉安等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為吉安企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

一、建Module

boot_redis01

boot_redis02

二、改POM



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.4
         
    
    com.lau
    boot_redis01
    0.0.1-SNAPSHOT
    boot_redis01
    Demo project for Spring Boot
    
        1.8
    
    
        
             org.springframework.boot
             spring-boot-starter-web
        
        
        
             org.springframework.boot
             spring-boot-starter-actuator
        
        
        
             org.springframework.boot
             spring-boot-starter-data-redis
        
        
        
             org.apache.commons
             commons-pool2
        
        
        
             redis.clients
             jedis
             3.1.0
        
        
        
             org.springframework.boot
             spring-boot-starter-aop
        
        
        
             org.redisson
             redisson
             3.13.4
        
        
             org.springframework.boot
             spring-boot-devtools
             runtime
             true
        
        
             org.projectlombok
             lombok
             true
        
        
             junit
             junit
             4.12
        
    
    
        
            
                 org.springframework.boot
                 spring-boot-maven-plugin
             
        
    

三、寫(xiě)YML

server.port=1111

spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
#連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)默認(rèn)8
spring.redis.lettuce.pool.max-active=8
#連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)默認(rèn)-1
spring.redis.lettuce.pool.max-wait=-1
#連接池中的最大空閑連接默認(rèn)8
spring.redis.lettuce.pool.max-idle=8
#連接池中的最小空閑連接默認(rèn)0
spring.redis.lettuce.pool.min-idle=0
?

四、主啟動(dòng)

package com.lau.boot_redis01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class BootRedis01Application {

    public static void main(String[] args) {
        SpringApplication.run(BootRedis01Application.class, args);
    }

}

五、業(yè)務(wù)類

1、RedisConfig配置類

package com.lau.boot_redis01.config;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String redisHost;

    /**
     *保證不是序列化后的亂碼配置
     */
    @Bean
    public RedisTemplateredisTemplate(LettuceConnectionFactory connectionFactory){
        RedisTemplate redisTemplate =new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    @Bean
    public Redisson redisson(){
       Config config = new Config();
            config.useSingleServer().setAddress("redis://"+redisHost+":6379").setDatabase(0);
       return (Redisson) Redisson.create(config);
    }
}

2、GoodController.java

package com.lau.boot_redis01.controller;

import com.lau.boot_redis01.util.RedisUtil;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class GoodController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

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

    private static final String REDIS_LOCK = "atguigulock";

    @GetMapping("/buy_goods")
    public String buy_Goods() throws Exception {
        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

        try{
            //1、key加過(guò)期時(shí)間是因?yàn)槿绻鹯edis客戶端宕機(jī)了會(huì)造成死鎖,其它客戶端永遠(yuǎn)獲取不到鎖
            //2、這里將setnx與鎖過(guò)期兩條命令合二為一,是為了解決命令分開(kāi)執(zhí)行引發(fā)的原子性問(wèn)題:
            //setnx  中間會(huì)被其它redis客戶端命令加塞   2、expire
            //3①、為了避免線程執(zhí)行業(yè)務(wù)時(shí)間大于鎖過(guò)期時(shí)間導(dǎo)致竄行操作,再釋放鎖時(shí)應(yīng)判斷是否是自己加的鎖;
            //還有另外一種解決方案:鎖續(xù)期——額外開(kāi)啟一個(gè)守護(hù)線程定時(shí)給當(dāng)前key加超時(shí)時(shí)間(如5s到期,每2.5s ttl判斷一次,并加2.5s超時(shí)時(shí)間,不斷續(xù)期,線程將使用主動(dòng)刪除key命令的方式釋放鎖;另,當(dāng)此redis客戶端命令宕機(jī)后,此守護(hù)線程會(huì)自動(dòng)隨之消亡,不會(huì)再主動(dòng)續(xù)期——此機(jī)制使得其它redis客戶端可以獲得鎖,不會(huì)發(fā)生死鎖或長(zhǎng)期等待)
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);//setnx

            if(!flag){
                return "獲取鎖失??!";
            }

            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if (goodsNumber > 0){
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");

                System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t  list = stringRedisTemplate.exec();
//
//                    if(list == null){
//                        continue;
//                    }
//                }
//
//                stringRedisTemplate.unwatch();
//                break;
//            }

            //解決方法2:lua腳本——原子操作
            Jedis jedis = RedisUtil.getJedis();

            String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then "
                    +"return redis.call('del', KEYS[1])"+"else "+ "  return 0 " + "end";
            try{
                Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));

                if ("1".equals(result.toString())){
                    System.out.println("------del REDIS_LOCK_KEY success");
                }
                else {
                    System.out.println("------del REDIS_LOCK_KEY error");
                }
            }finally {
                if (null != jedis){
                    jedis.close();
                }
            }
        }
    }
}

六、改造中的問(wèn)題

1、單機(jī)版沒(méi)加鎖

 問(wèn)題:沒(méi)有加鎖,并發(fā)下數(shù)字不對(duì),會(huì)出現(xiàn)超賣現(xiàn)象

① synchronized 不見(jiàn)不散

② ReentrantLock 過(guò)時(shí)不候

 
在單機(jī)環(huán)境下,可以使用synchronized或Lock來(lái)實(shí)現(xiàn)。
 
但是在分布式系統(tǒng)中,因?yàn)楦?jìng)爭(zhēng)的線程可能不在同一個(gè)節(jié)點(diǎn)上(同一個(gè)jvm中),所以需要一個(gè)讓所有進(jìn)程都能訪問(wèn)到的鎖來(lái)實(shí)現(xiàn),比如redis或者zookeeper來(lái)構(gòu)建;
 
不同進(jìn)程jvm層面的鎖就不管用了,那么可以利用第三方的一個(gè)組件,來(lái)獲取鎖,未獲取到鎖,則阻塞當(dāng)前想要運(yùn)行的線程

2、使用Nginx配置負(fù)載均衡

注:分布式部署后,單機(jī)鎖還是出現(xiàn)超賣現(xiàn)象,需要分布式鎖

啟動(dòng)兩個(gè)微服務(wù)1111和2222,訪問(wèn)使用:http://localhost/buy_goods(即通過(guò)nginx輪詢方式訪問(wèn)1111和2222兩個(gè)微服務(wù))

nginx.conf配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
	upstream mynginx{#反向代理的服務(wù)器列表,權(quán)重相同,即負(fù)載均衡使用輪訓(xùn)策略
		server localhost:1111 weight=1;
		server localhost:2222 weight=1;
	}

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            #root   html;
            #index  index.html index.htm;
			proxy_pass http://mynginx;#配置反向代理
			index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

3、異常將導(dǎo)致鎖不會(huì)釋放

① 出異常的話,可能無(wú)法釋放鎖, 必須要在代碼層面finally釋放鎖 

② 加鎖解鎖,lock/unlock必須同時(shí)出現(xiàn)并保證調(diào)用

4、宕機(jī)

① 部署了微服務(wù)jar包的機(jī)器掛了,代碼層面根本沒(méi)有走到finally這塊, 沒(méi)辦法保證解鎖,這個(gè)key沒(méi)有被刪除,需要加入一個(gè)過(guò)期時(shí)間限定key

② 需要對(duì)lockKey有過(guò)期時(shí)間的設(shè)定

5、設(shè)置key+過(guò)期時(shí)間分開(kāi)

① 設(shè)置key+過(guò)期時(shí)間分開(kāi)了,必須要合并成一行具備原子性

6、張冠李戴,刪除了別人的鎖

① 設(shè)置鎖失效時(shí)間不合理

7、finally塊的判斷+del刪除操作不是原子性的

① 用redis自身的事務(wù)

i 未使用watch前:

如何用redis來(lái)實(shí)現(xiàn)分布式鎖

如何用redis來(lái)實(shí)現(xiàn)分布式鎖

ii使用watch后:

如何用redis來(lái)實(shí)現(xiàn)分布式鎖

如何用redis來(lái)實(shí)現(xiàn)分布式鎖

② 用Lua腳本

Redis可以通過(guò)eval命令保證代碼執(zhí)行的原子性

java配置類:

package com.lau.boot_redis01.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    private static JedisPool jedisPool;

  static {
   JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);

        jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379,100000);
    }

    public static Jedis getJedis() throws Exception{
        if (null!=jedisPool){
            return jedisPool.getResource();
        }
        throw new Exception("Jedispool is not ok");
    }
}
Jedis jedis = RedisUtil.getJedis();

            String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then "
                    +"return redis.call('del', KEYS[1])"+"else "+ "  return 0 " + "end";
            try{
                Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));

                if ("1".equals(result.toString())){
                    System.out.println("------del REDIS_LOCK_KEY success");
                }
                else {
                    System.out.println("------del REDIS_LOCK_KEY error");
                }
            }finally {
                if (null != jedis){
                    jedis.close();
                }
            }

8、仍然存在的問(wèn)題(redisson得以解決)

① Redis分布式鎖如何續(xù)期? 確保redisLock過(guò)期時(shí)間大于業(yè)務(wù)執(zhí)行時(shí)間的問(wèn)題(鎖續(xù)期)

② redis單點(diǎn)故障——redis異步復(fù)制造成的鎖丟失, 比如:主節(jié)點(diǎn)沒(méi)來(lái)的及把剛剛set進(jìn)來(lái)這條數(shù)據(jù)給從節(jié)點(diǎn),就掛了。(zk/cp、redis/ap)(redis集群)

確保redisLock過(guò)期時(shí)間大于業(yè)務(wù)執(zhí)行時(shí)間的問(wèn)題;redis集群環(huán)境下,我們自己寫(xiě)的也不OK, 直接上RedLock之Redisson落地實(shí)現(xiàn)

1、RedisConfig.java

    @Bean
    public Redisson redisson(){
       Config config = new Config();
            config.useSingleServer().setAddress("redis://"+redisHost+":6379").setDatabase(0);
       return (Redisson) Redisson.create(config);
    }

2、控制器類: 

package com.lau.boot_redis01.controller;

import com.lau.boot_redis01.util.RedisUtil;
import lombok.val;
import org.redisson.Redisson;
import org.redisson.RedissonLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class GoodController_Redisson {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

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

    private static final String REDIS_LOCK = "atguigulock";

    @Autowired
    private Redisson redisson;

    @GetMapping("/buy_goods2")
    public String buy_Goods() throws Exception {
        String value = UUID.randomUUID().toString() + Thread.currentThread().getName();

        RLock lock = redisson.getLock(REDIS_LOCK);

        try{
            lock.lock();

            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if (goodsNumber > 0){
                int realNumber = goodsNumber - 1;
                stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");

                System.out.println("你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口: "+serverPort);

                return "你已經(jīng)成功秒殺商品,此時(shí)還剩余:" + realNumber + "件"+"\t 服務(wù)器端口: "+serverPort;
            }

            System.out.println("商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口: "+serverPort);

            return "商品已經(jīng)售罄/活動(dòng)結(jié)束/調(diào)用超時(shí),歡迎下次光臨"+"\t 服務(wù)器端口: "+serverPort;
        }
        finally {
            //IllegalMonitorStateException:attempt unlock lock,not locked by current thread by node_id
            if(lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }
}

注:在并發(fā)多的時(shí)候就可能會(huì)遇到這種錯(cuò)誤,可能會(huì)被重新?lián)屨?/p>

 如何用redis來(lái)實(shí)現(xiàn)分布式鎖

到此,相信大家對(duì)“如何用redis來(lái)實(shí)現(xiàn)分布式鎖”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!


本文題目:如何用redis來(lái)實(shí)現(xiàn)分布式鎖
標(biāo)題路徑:http://weahome.cn/article/pcjiph.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部