• 高阶玩家需要掌握的


    说说自己的理解,结合背景、业务、代码谈技术

    • 分布式系统

    结合自己之前做过的业务系统,有这样一个业务场景,也是比较常见的,销售、代理商需要登录系统去给商户开通相应的产品功能;

    这个时候比较容易想到的几个业务场景,提单、拆单、工单审批、审批的过程中还需要一系列的RPC、消息交互等等

    如果这么多个场景耦合在一起,在一个系统中能实现么?答案是,当然可以,刚毕业那会的项目可能就是都这么做的;

    耦合性比较高,业务之间紧密相连,这样做的好处就是事务处理就简单了,本地事务,利用数据库的事务就能帮你解决你会遇到的所有事务问题;

    但是随之而来的就是高风险,万一哪个功能点出现问题,系统就挂了,代码会极其多,量变就会产生质变,一旦有功能的加入、或者老人的离职

    这个将会是灾难性的,对于新人的入手也会难上加难,所以凡事都有两面性,结合自身的业务需要去做技术选型,脱离业务谈技术就是耍流氓!

    这个时候常见的做法就是把巨无霸拆,拆成多个系统,各个系统之间通过网络通讯来完成交互,就是原来的本地API调用,转变成了跨进程,跨服务器的远程调用;

    这样子做一下子清晰明了,但是随之而来的就是系统之间错综复杂的调用,事务的一致性无法都到保证,原来只要维护很少的应用现在一下子多了N个。。

    即使有上述很多问题,大家还是愿意这样去做,为啥?个人觉得就是现在有很多成熟的开源框架供人们去使用,接入成本大大降低;

    想想看,如果没有这些开源的或者商用的技术服务,你敢去拆,一个服务的调用稳定、容灾、并发等等都是需要我们去考虑的等等,就需要大量的中间件去帮我们去屏蔽这些细节!

    那么分布式系统出来了,下面就是随之而来的分布式锁、分布式事务、RPC、消息队列、全局ID等一系列高阶玩家需要去摸索和了解的;

    • 分布式锁

     我们平常见的比较多的可能就是lock、sychronized这些都是在一个进程中的多线程实现,一旦我们集群了这种加锁的方式就会失效,因为他不能跨JVM,

    这个时候我们就需要借助第三方的东西帮我们实现功能

    目前主流的有三种,从实现的复杂度上来看,从上往下难度依次增加:

    基于数据库实现

    基于Redis实现

    基于ZooKeeper实现

    无论哪种方式,其实都不完美,依旧要根据咱们业务的实际场景来选择。

    我们先来看一下如何基于「乐观锁」来实现:

    乐观锁就是认为我在执行的时候乐观的以为其他人都不会执行,只有我在执行这更新操作;

    乐观锁机制其实就是在数据库表中引入一个版本号(version)字段来实现的。

      当我们要从数据库中读取数据的时候,同时把这个version字段也读出来,如果要对读出来的数据进行更新后写回数据库,则需要将version加1,同时将新的数据与新的version更新到数据表中,

    且必须在更新的时候同时检查目前数据库里version值是不是之前的那个version,如果是,则正常更新。如果不是,则更新失败,说明在这个过程中有其它的进程去更新过数据了。

     

    下面找图举例,

     

    如图,假设同一个账户,用户A和用户B都要去进行取款操作,账户的原始余额是2000,用户A要去取1500,用户B要去取1000,

    如果没有锁机制的话,在并发的情况下,可能会出现余额同时被扣1500和1000,导致最终余额的不正确甚至是负数。

    但如果这里用到乐观锁机制,当两个用户去数据库中读取余额的时候,除了读取到2000余额以外,还读取了当前的版本号version=1,

    等用户A或用户B去修改数据库余额的时候,无论谁先操作,都会将版本号加1,即version=2,

    那么另外一个用户去更新的时候就发现版本号不对,已经变成2了,不是当初读出来时候的1,那么本次更新失败,就得重新去读取最新的数据库余额。

     

    通过上面这个例子可以看出来,使用「乐观锁」机制,必须得满足:

     

    (1)锁服务要有递增的版本号version

    (2)每次更新数据的时候都必须先判断版本号对不对,然后再写入新的版本号

     

    我们再来看一下如何基于「悲观锁」来实现:

    悲观锁就是我在执行的时候,别人也会去执行,所以我必须把他锁起来,我再操作,不然我不做更新;

    悲观锁也叫作排它锁,在Mysql中是基于 for update 来实现加锁的,例如:

     

    //锁定的方法-伪代码

    public boolean lock(){
    
        connection.setAutoCommit(false)
    
       for(){
    
      result =select * from user where id = 100 for update;
    
      if(result){
    
        //结果不为空,
    
        //则说明获取到了锁
    
        return true;
    
      }
    
      //没有获取到锁,继续获取
    
      sleep(1000);
    
      }
    
      return false;
    
    }
    
    //释放锁-伪代码
    
    connection.commit();

    上面的示例中,user表中,id是主键,通过 for update 操作,数据库在查询的时候就会给这条记录加上排它锁。

    (需要注意的是,在InnoDB中只有字段加了索引的,才会是行级锁,否者是表级锁,所以这个id字段要加索引)

    当这条记录加上排它锁之后,其它线程是无法操作这条记录的。

    那么,这样的话,我们就可以认为获得了排它锁的这个线程是拥有了分布式锁,然后就可以执行我们想要做的业务逻辑,当逻辑完成之后,再调用上述释放锁的语句即可。

    给大家看下如何去模拟这个排他锁,直接上截图,有图有真相;Mysql常用的客户端一般都是navicat;  

    比如我们没有上锁,select * from t_shark_user t WHERE t.user_name='1111'(这种操作天生幂等),无论谁去查都OK,但是如果你加了 for update

    变成select * from t_shark_user t WHERE t.user_name='1111' for update;这个就会有好玩的情境出现了;

    首先,执行下面的命令看下你的自动提交时打开还是关闭的,默认是打开的,就是你每执行一个SQL,mysql会自动帮你commit,不需要你手动进行commit;

    如果你的是自动提交的,你for update也没用,因为你刚锁完,他就帮你提交了,也就释放锁了;

    show variables like 'autocommit';

    为了模拟排他锁,执行命令  set autocommit = 0;这个时候Value就会变成OFF;

    在其中一个窗口(session)中执行,怎么执行都是OK的,这条数据,因为这个会话已经获取了该条数据的行级锁,

    但是新打开一个窗口,就会不一样了,不信继续往下看:一直在等待,当然他会有个超时时间;

     一直没有结果;select * from t_shark_user t WHERE t.user_name='张书' for update;

     但是查询另外一条数据是OK的,

    利用redis实现分布式锁:

    大家可以参考下面的博主分享的文章   https://www.lovecto.cn/20180814/203.html,亲测可以用;直接上代码

    package com.cloudwalk.shark.config.redisson;
    
    import com.cloudwalk.shark.config.inter.impl.RedissonLocker;
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    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 java.io.IOException;
    
    
    @Configuration
    public class RedissonConfig {
    
        @Value("${mini.redis.host}")
        private String host;
        @Value("${mini.redis.port}")
        private String port;
        @Value("${mini.redis.passwd}")
        private String password;
    
        /**
         * RedissonClient,单机模式
         * @return
         * @throws IOException
         */
        @Bean(destroyMethod = "shutdown")
        public RedissonClient redisson() throws IOException {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
            return Redisson.create(config);
        }
    
        @Bean
        public RedissonLocker redissonLocker(RedissonClient redissonClient){
            RedissonLocker locker = new RedissonLocker(redissonClient);
            //设置LockUtil的锁处理对象
            LockUtil.setLocker(locker);
            return locker;
        }
    }
    package com.cloudwalk.shark.config.redisson;
    
    import com.cloudwalk.shark.config.inter.Locker;
    
    import java.util.concurrent.TimeUnit;
    
    
    /**
     * redis分布式锁工具类
     *
     */
    public final class LockUtil {
    
        private static Locker locker;
    
        /**
         * 设置工具类使用的locker
         * @param locker
         */
        public static void setLocker(Locker locker) {
            LockUtil.locker = locker;
        }
    
        /**
         * 获取锁
         * @param lockKey
         */
        public static void lock(String lockKey) {
            locker.lock(lockKey);
        }
    
        /**
         * 释放锁
         * @param lockKey
         */
        public static void unlock(String lockKey) {
            locker.unlock(lockKey);
        }
    
        /**
         * 获取锁,超时释放
         * @param lockKey
         * @param timeout
         */
        public static void lock(String lockKey, int timeout) {
            locker.lock(lockKey, timeout);
        }
    
        /**
         * 获取锁,超时释放,指定时间单位
         * @param lockKey
         * @param unit
         * @param timeout
         */
        public static void lock(String lockKey, TimeUnit unit, int timeout) {
            locker.lock(lockKey, unit, timeout);
        }
    
        /**
         * 尝试获取锁,获取到立即返回true,获取失败立即返回false
         * @param lockKey
         * @return
         */
        public static boolean tryLock(String lockKey) {
            return locker.tryLock(lockKey);
        }
    
        /**
         * 尝试获取锁,在给定的waitTime时间内尝试,获取到返回true,获取失败返回false,获取到后再给定的leaseTime时间超时释放
         * @param lockKey
         * @param waitTime
         * @param leaseTime
         * @param unit
         * @return
         * @throws InterruptedException
         */
        public static boolean tryLock(String lockKey, long waitTime, long leaseTime,
                                      TimeUnit unit) throws InterruptedException {
            return locker.tryLock(lockKey, waitTime, leaseTime, unit);
        }
    
        /**
         * 锁释放被任意一个线程持有
         * @param lockKey
         * @return
         */
        public static boolean isLocked(String lockKey) {
            return locker.isLocked(lockKey);
        }
    }
    package com.cloudwalk.shark.controller;
    
    import com.cloudwalk.shark.config.redisson.LockUtil;
    import com.cloudwalk.shark.model.User;
    import com.cloudwalk.shark.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.util.concurrent.TimeUnit;
    
    @Controller
    @RequestMapping(value = "/lock", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public class SharkDBLockController {
        @Autowired
        private UserService userService;
        @PostMapping("/db/{userName}")
        @ResponseBody
        public void updateUserScore(@PathVariable("userName") String userName) throws InterruptedException {
            if(LockUtil.tryLock("1", 1,5,TimeUnit.SECONDS) ){
                System.out.println("接收到请求"+userName);
                User user = userService.findUserByName(userName);
                Thread.sleep(2000);
                user.setScore(user.getScore() + 1);
                userService.updateUser(user);
            }else{
                System.out.println("指定时间内没有获取到相应的锁!");
            }
        }
    }

    通过调节,waitTime,以及leaseTime可以看到效果;

    /**
    * 尝试获取锁,在给定的waitTime时间内尝试,获取到返回true,获取失败返回false,获取到后再给定的leaseTime时间超时释放
    * @param lockKey
    * @param waitTime
    * @param leaseTime
    * @param unit
    * @return
    * @throws InterruptedException
    */
    public static boolean tryLock(String lockKey, long waitTime, long leaseTime,
    TimeUnit unit) throws InterruptedException {
    return locker.tryLock(lockKey, waitTime, leaseTime, unit);
    }

    分布式事务

    分布式锁

    消息队列

    RPC

    全局唯一ID生成策略

  • 相关阅读:
    数字三角形W(加强版) codevs 2189
    数字三角形W(加强版) codevs 2189
    线段树模板
    树状数组模板 洛谷3374
    洛谷 2327 [SCOI2005]扫雷
    洛谷 2327 [SCOI2005]扫雷
    洛谷1144 最短路计数
    洛谷1144 最短路计数
    洛谷1346 电车
    洛谷1346 电车
  • 原文地址:https://www.cnblogs.com/longxok/p/10937601.html
Copyright © 2020-2023  润新知