面试题总结 20231024

news2024/11/16 15:27:39

1.桶排序的应用案例:上亿元素排行榜

 step1: 桶排序本质是一种分治算法

step2:每个桶都代表了一个元素的范围

step3:每个桶中的元素都排好序后,取出来,这样子就有序了

2.简述你们框架中用到的线程模型

1.actor思想(单线程处理)

2.xdb加锁(类似的还有mysql的锁机制)

3.解释下你们xdb中get方法拿锁的流程 和 拿锁是tryLock还是lock,业务执行过长怎么办?时用到的锁是什么?(以秘宝为例子)

1.首先是GatewayHandler收到玩家请求

public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            if(msg == null) {
                return;
            }

            if(GameServer.isServerClosed()) {
                return;
            }

            ProtobufMessage message = (ProtobufMessage) msg;
            session.parseAndDispatchMessage(message);
}

2.ClientSession是绑定了根据userId,玩家登录后就也确定了roleId。 所以将上一步的请求和这个玩家信息封装为上下文如:OnlineMsgParam,然后嵌套Instance,扔到xdb线程池中执行

 // 这个是使用xdb.Executor.getInstance()是单例类,是支持了Timeout
        xdb.Executor.getInstance().execute(() ->
                client2LogicMsgInstance.handle(message, "userId", userId));

其中这个xdb.Executor.getInstance()是一个支持超时的线程池TimeoutExecutor

execute方法:

	public void execute(Runnable command) {
		super.execute(xdb.Angel.decorateRunnable(command, defaultTimeout));
	}

可见这是一个被包装为支持超时的任务了。

3.这个任务中很可能是一个修改操作,比如:领奖,那么这个业务Task被执行时,里面肯定是触发了一个submit的提交,咱们看下实现:

    @MsgReceiver(MsgArtifact.CSAddPositionExp.class)
    public static void onCSAddPositionExp(OnlineMsgParam param) {
         
        // 这个业务体肯定是在xdb线程中,同时也接收了超时检测,但是我想这个超时检测仅仅是:
        // 这个方法体的,毕竟submit的执行还是异步的。所以一般不会超时,除非带有select这种超时了

        new PAddArtifactBaseExp(param.getHumanId(), equips).submit();
    }

submit的实现如下:

    public final Future<Procedure> submit() {
        // 首先验证下是不是已经在事务内了,在事务内了是不能submit了
        verify();

        // 看着是new了一个ProcedureFuture对象,实际还是任务的提交
        return new ProcedureFuture<Procedure>(this);
    }

看下构造方法:

    public ProcedureFuture(P p) {
        // 保存下事务
        this.p = p;

        // 扔到带有超时检测的线程池中
        // 实现的是Future接口
        future = Xdb.executor().getProcedureTimeoutExecutor()
                .submit(this, p, p.getConf().getMaxExecutionTime());

        // 默认情况下,啥都不干
        this.done = null;
    }

可见是把当前事务又包装了下,这个submit是自己写的,注意:主角Angle登场了。

其实还是调用的java线程池的submit,只不过是多了一层Angle的包装:

	public <T> Future<T> submit(Runnable task, T result, long timeout) {
		xdb.Worker.debugHunger(this);
		return super.submit(xdb.Angel.decorate(task, result, timeout));
	}

这个Angle包装下是为了啥呢,接下来看:

  public static <V> Callable<V> decorate(Runnable task, V result, long timeout) {
        // 将Runnable包装为Callable
        final Callable<V> callable = Executors.callable(task, result);

        // 默认肯定是有超时检测的,因此这里就是:TimeoutCallable
        return timeout > 0 ? new TimeoutCallable<>(callable, timeout) : callable;
    }

可见是为了这个TimeoutCallable又进行了包装(又使用TimeoutManager带上了超时检测)

    @Override
    public V call() throws Exception {
        if (timeout > 0) {
            runner = Thread.currentThread();

            // 将此任务开始执行前扔到TimeoutManager中,如果到时候没移除,则说明此任务执行超时
            final TimeoutManager tm = TimeoutManager.getInstance();
            tm.schedule(this, timeout);

            try {
                // 任务真正开始执行
                return inner.call();
            } finally {
                // 执行完毕后,移除
                tm.remove(this);
                runner = null;
            }
        } else {
            return inner.call();
        }
    }

至此,是不是有点迷糊了,再次后头看一下,其实这个inner.call()执行的实际是啥呢?其实就是:

ProcedureFuture的方法体,也就是最初:new ProcedureFuture的地方,我们看下这个执行体(只不过这个执行体又被包装了支持了超时检测)

@Override
    public void run() {
        // 存储过程开始执行,执行次数+1
        ++ranTimes;

        try {
            // 创建事务和执行存储过程。
            try {
                // 核心方法:所以这个里面调用的还是call方法
                Transaction.create().perform(p);
            } finally {
                // safe if create fail
                Transaction.destroy();
            }

            // 正常存储过程执行结束
            // 但是目前看done变量为空,等于这句啥也没做
            done();
        } catch (XLockDead e) { // 这个异常何时被抛出来呢?其实就是:Lockkey中的无参lock方法使用的是lockInterruptibly,在被超时打断的时候会跑出来
            /** @see Lockey#lock() */

            // 重试次数过多
            if (ranTimes >= p.getConf().getRetryTimes()) {
                done();

                // 达到最大重复次数.报告最终错误.
                throw new XAngelError();
            }

            // 下面是发生死锁了,随机一个时间,进行重试
            int delay = Xdb.random().nextInt(p.getConf().getRetryDelay());

            // 再次提交任务到线程池
            future = Xdb.executor().getScheduledTimeoutExecutor().schedule(
                    Executors.callable(this, p),
                    delay,
                    TimeUnit.MILLISECONDS,
                    p.getConf().getMaxExecutionTime());

            // 报告死锁错误,future打断当前的监视对象,重新监视。
            throw e;
        } catch (Error error) {
            done();
            throw error;
        } catch (Throwable e) {
            done();
            // 有其他方法,不需要包装一下,直接扔出去吗?
            throw new XError(e);
        }
    }

我们看下Transaction的perform方法干了啥?其实最重要的就是调用了call方法,从而创建事务,

并且执行我们的process方法

 public void perform(Procedure p) throws Throwable {
        try {
            // 总数 = .True(未统计此项) + .False + .Exception
            //counter.increment(p.getClass().getName());
            totalCount.incrementAndGet();

            // flush lock . MEMORY类型的表本来不需要这个锁,为了不复杂化流程,不做特殊处理。
            Lock flushLock = Xdb.getInstance().getTables().flushReadLock();
            flushLock.lockInterruptibly();
            try {
                // 重点方法call!!!
                if (p.call()) {
                    if (_real_commit_() > 0) {
                        logNotify(p);
                        // else : 没有修改,不需要logNotify。至此过程处理已经完成了。
                    }
                } else {
                    // 执行逻辑返回false统计
                    //counter.increment(p.getClass().getName() + ".False");
                    totalFalse.incrementAndGet();
                    _last_rollback_(); // 应用返回 false,回滚
                }
            } catch (Throwable e) {
                // 未处理的异常,回滚
                _last_rollback_();
                throw e;
            } finally {
                // 有多把锁
                if (deadLockDetection && lockList.size() > 1) {
                    //死锁风险检测
                    deadlockDetection(p.getClass().getName());
                }

                this.doneRunAllTask();
                this.finish();
                flushLock.unlock();
            }

        } catch (Throwable e) {
            p.setException(e);
            p.setSuccess(false);
            // 执行异常统计
            //counter.increment(p.getClass().getName() + ".Exception");
            totalException.incrementAndGet();
            // 所有的异常错误都应该处理,尽量不抛到这里。这里仅记录日志。
            Trace.error("Transaction Perform Exception " + p.getClass().getName(), e);
            throw e;
        }
    }

然后进入到高潮部分,也就call方法,也就是调用我们业务层的process,从而真正业务执行部分(同时在业务异常或者返回false时将本地缓存回滚,也就是log删除掉)

public boolean call() {
        // 当前如果不在事务内
        if (Transaction.current() == null) {
            try {
                // perform 将回调本函数,然后执行事务已经存在的分支。
                // 何时被加上事务的呢?其实就是这个Transaction.create()中执行的,当前ThreadLocal没存,则设置下
                Transaction.create().perform(this);
            } catch (Throwable e) {
                // this.setException(e); 在 Transaction.perform 里面会保存异常。这里没什么事可做了。
            } finally {
                Transaction.destroy();
                this.fetchTasks();
            }
            return this.isSuccess();
        }

        // 执行到这里必然是处于事务中了,则记录下保存点
        int savepoint = beginAndSavepoint();

        // 捕捉所有异常,在发生异常和process返回false时,回滚到过程开始的保存点。
        // 不捕捉错误,所有的错误抛到外层。
        try {
            if (process()) {
                commit();
                this.setSuccess(true);
                return true;
            }
        } catch (Exception ex) {
            this.setException(ex);
            logErrorFunc.accept(ex);
        }

        // 进行业务的回滚
        rollback(savepoint);

        return false;
    }

4.理解xdb中怎么拿锁的

经过上面的分析,我们看出来,其实就是业务的执行时,仅仅是使用ThreadLocal保存了当前new出来的事务对象,然后接着执行我们的process方法了,里面就是我们游戏层的业务实现了,我们分析下process方法如何拿锁的,我们直接看秘宝的process方法:

1.映入眼帘的肯定是这一句

ArtifactBean artifactBean = Artifact.get(humanId);

2.看一下get方法

	public static xbean.ArtifactBean get(Long key) throws Exception {
		return _Tables_.getInstance().artifact.get(key);
	}

3.这个方法是重载的

public final V get(K key) throws Exception {
        return get(key, true);
    }

4.接下来看实现

    public final V get(K key, boolean holdNull) throws Exception {
        if (null == key) {
            throw new NullPointerException("key is null");
        }

        countGet.incrementAndGet();

        // 从事务本身里先进行查询,也就是在事务内拿过一次锁之后,以后不会再重复拿了
        final Transaction currentT = Transaction.current();

        // 先从本地事务哪个普通的Map对象中拿缓存.这段代码算是优化了
        TRecord<K, V> rCached = currentT.getCachedTRecord(this, key);
        if (rCached != null) {
            return rCached.getValue();
        }

        // 事务缓存中没拿到,就生成一把锁
        Lockey lockey = Lockeys.get(this, key);

        // 这里调用的是:lockInterruptibly方法
        lockey.lock();
        try {
            // 这个是实现LRU算法的,从本地缓存取数据,对应的类是:TTableCacheLRU,取缓存会有一次synchronized的调用,所以上面还有一份事务内的缓存加快访问
            // LRU底层是:包装的LinkedHashMap实现,删除策略是自己实现的
            TRecord<K, V> r = cache.get(key);

            // 缓存中不存在
            if (null == r) {
                // 记录下缓存miss了
                countGetMiss.incrementAndGet();

                // 缓存中也没有,那就查询sql了
                V value = _find(key);

                // sql也没查询到
                if (null == value) {
                    countGetStorageMiss.incrementAndGet();
                    if (holdNull) {
                        currentT.add(lockey);
                    }
                    return null;
                }

                // sql查询到了
                r = new TRecord<K, V>(this, value, lockey, TRecord.State.INDB_GET);

                // 先记录数据到LRU缓存中
                cache.addNoLog(key, r);
            }

            // 下面其实是记录一次缓存到本地事务中,这样子不是每次都从LRU cache中取,可以减少一次锁的访问
            // 重点:记录当前事务拿到的锁! 死锁检测就从这里入手了
            // 注意: 里面还是有一次lock。 所以下面finally会先unlock一次
            currentT.add(lockey);

            // 记录下本次事务用过的缓存
            currentT.addCachedTRecord(this, r);

            // 返回取到的值
            return r.getValue();
        } finally {
            lockey.unlock();
        }
    }

分析:

可以看出来,当前缓存的话,是根据Transaction中一个普通的Map中拿到的,首先根据当前的表名字和key取缓存,毕竟会涉及到多张表,所以是要传入表对象的。

这样子其实有2份缓存:1份是事务内的缓存,一份是:根据表+synchronized。

这样子拿过缓存后,就不会再加锁了直接取缓存。否则会查询缓存。

缓存查询不到,查sql,查到了,则记录到本地事务中

前面分析了,何时释放所有的锁呢?

Transaction.java

    /**
     * 结束事务,释放所有锁并且清除,清除wrapper。
     */
    private void finish() {
        wrappers.clear();

        // 没有按照lock的顺序unlock。
        for (Lockey lockey : locks.values()) {
            // Trace.debug("unlock " + lockey);
            try {
                lockey.unlock();
            } catch (Throwable e) {
                Trace.fatal("unlock " + lockey, e);
            }
        }
        locks.clear();
        cachedTRecord.clear();
    }

5.理解xdb中的超时检测机制

如何进行超时检测呢?

在Exector初始化的时候,就负责开启了一个定时器进行超时检测的处理

      this.scheduled.scheduleWithFixedDelay(
                // 执行超时检测
                xdb.util.TimeoutManager.getInstance(),

                timeoutPeriod,
                timeoutPeriod,
                TimeUnit.MILLISECONDS);

检测到超时后,进行打断,咱们的Xdb线程池中的线程都是Worker线程:

 @Override
    public void onTimeout() {
        final Thread r = runner;

        // 如果这个r为null,则说明已经执行完了
        if (r != null) {
            if (r instanceof Worker) {
                ((Worker) r).angelInterrupt();
            } else {
                r.interrupt();
            }
        }
    }

打断方法实现如下:

   /**
     * 这个是Worker被打断时,多执行一个标记
     */
    public void angelInterrupt() {
        angel.set(true);
        super.interrupt();
    }

Lockey中使用的是可被打断的lockInterruptibly:

    /**
     * 这个非常的重要,
     */
    public final void lock() {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException ex) {
            // 这里我认为其实未必就是真的死锁,有可能业务执行繁忙超时后,也会被打断?
            if (Worker.angelInterrupted()) {
                throw new XLockDead();
            }

            throw new XLockInterrupted(this.toString());
        }
    }

3.这样子在ArtifactMsgHandler的一个方法处理器中,就会到一个xdb线程池中执行。

6.简述Recast Nav中障碍的实现

1.

7.简述技能系统的实现

1.

8.压测都发现了什么问题?

1.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1130331.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

T01西门子#DPRD_DAT

关键字&#xff1a;无系统常数&#xff1b;目前没有可显示的系统常数&#xff1b;没有设备标识符。 西门子300PLC在博图V15.1中是没有系统常数&#xff0c;没有设备标识符。 在博图V15.1中使用DPRD_DAT时&#xff0c;LADDR参数如何选择&#xff1f; LADDR为待读取数据的 模块的…

CSAPP 练习题 2.25

#include<stdio.h>float sum_elements(float a[], unsigned length) {int i;float result 0;for (i 0; i < length-1; i) {result a[i];}return result; }int main() {float a[0];printf("array sum: %f\n", sum_elements(a, 0)); } 调整代码后&#xff…

ChatGLM系列一——ChatGLM的介绍与使用

下面是官方对ChatGLM的介绍&#xff1a; ChatGLM-6B&#xff0c;结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&#xff09;。经过约 1T 标识符的中英双语训练&#xff0c;辅以监督微调、 反馈自助、人类反…

权限系统设计(转载)

1 为什么需要权限管理 2 权限模型 2.1 权限设计 2.2 为什么需要角色 2.3 权限模型的演进 2.4 用户划分 2.5 理想的RBAC模型 3 权限系统表设计 3.1 标准RBAC模型表设计 3.2 理想RBAC模型表设计 4 结语 1 为什么需要权限管理 日常工作中权限的问题时时刻刻伴随着我们&a…

白嫖在线云服务器,免费在 linux 服务器使用 docker 。 附视频+附文档

白嫖在线云服务器&#xff0c;免费在 linux 服务器使用 docker 。 附视频附文档 文章目录 前言启动 Nginx 案例最后 视频&#xff1a;https://www.bilibili.com/video/BV1WN411W79V/ 国内互联网经过多年发展&#xff0c;单体应用已经无法支持我们的互联网业务&#xff0c;分布…

css面试题及答案 【集合目录】

前言&#xff1a; 欢迎浏览和关注本专栏《 前端就业宝典 》&#xff0c; 不管是扭螺丝还是造火箭&#xff0c; 多学点知识总没错。 这个专栏是扭螺丝之上要造火箭级别的知识&#xff0c;会给前端工作学习的小伙伴带来意想不到的帮助。 本专栏将前端知识拆整为零&#xff0c;主要…

Redis单Reactor单线程网络模型

Redis单线程单Reactor网络模型 redis单线程里不能执行十分耗时的流程&#xff0c;不然会客户端响应不及时 解决方法一&#xff1a; beforesleep里删除过期键操作若存在大量过期键时&#xff0c;会耗费大量时间&#xff0c;redis采用的策略之一就是采用timelimit方案超过阈值就…

Hadoop部署过程中问题总结

Hadoop伪分布式集群部署问题总结 一、HDFS初始化失败 初始化失败&#xff0c;HDFS初始化操作如下&#xff1a; hdfs namenode -format然后我运行后报错&#xff1a;error parsing conf core-site.xml 出现这个信息就说明core-site.xml配置文件出错了&#xff0c;用vim命令可…

【AI视野·今日NLP 自然语言处理论文速览 第五十八期】Thu, 19 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 19 Oct 2023 Totally 74 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Understanding Retrieval Augmentation for Long-Form Question Answering Authors Hung Ting Chen, Fangyuan…

STM32 TIM(一)定时中断

STM32 TIM&#xff08;一&#xff09;定时中断 定一个时间&#xff0c;然后让定时器每隔这个时间产生一个中断&#xff0c;来实现每隔一个固定时间执行一段程序的目的&#xff0c;比如你要做个时钟、秒表&#xff0c;或者使用一些程序算法的时候&#xff0c;都需要用到定时中断…

06数据结构——图

6.2图的存储及基本操作 6.2.1邻接矩阵法 图的邻接矩阵存储结构定义如下&#xff1a; #define MaxVertexNUm 100 //顶点数目的最大值 typedef char VertexType; //顶点的数据类型 typedef int EdgeType; //带权图中边上权值的数据类型 ty…

webrtc-stream编译报错记录

磁盘空间不足错误 错误信息 677.2 fatal: cannot create directory at blink/web_tests/external/wpt: No space left on device说明&#xff1a;这个错误是由于本地在配置docker资源时所给磁盘空间太小导致&#xff0c;直接根据镜像大小合理分配资源大小即可 pushd和popd执…

Android Studio新功能-设备镜像Device mirroring-在电脑侧显示手机实时画面并可控制

下载最新的灰测版本-蜥蜴 成功运行到真机后&#xff0c;点击右侧Running Devices选项卡&#xff0c;再点击号 选中当前设备&#xff1b; 非常丝滑同步&#xff0c;在电脑侧也可以顺畅控制真机 该功能大大方便了我们视线保持在显示器上专注开发&#xff0c;并且便于与UI视觉进行…

【Rust日报】2023-10-22 Korvin - 一个 WASM 前端框架,比基线 vanillajs 实现快了 33%!...

Yazi v0.1.5 发布 - 有史以来最大的更新 Yazi - &#x1f4a5; 用 Rust 编写的基于异步 I/O 的快速终端文件管理器。 嘿伙计&#xff01;我很高兴在这里宣布Yazi v0.1.5 发布了&#xff01; 这是有史以来最大的更新&#xff0c;也是周期最长的更新。该版本带来了许多有意义的变…

【进程VS容器VS虚拟机】

进程 VS 容器 VS 虚拟机 如果站在技术实现原理的角度来看&#xff0c;其实容器更像进程&#xff0c;而非虚拟机。 但是如果我们讨论这门技术的应用场景、解决的问题、终端用户是如何使用的&#xff0c;就会发现容器跟虚拟机非常相似&#xff0c;它们解决的是同样的问题&#…

渗透测试工具-sqlmap

sqlmap是一个开源渗透测试的自动化工具&#xff0c;可以自动检测和利用SQL注入漏洞并接管数据库服务器。它配备了一个强大的检测引擎&#xff0c;许多用于终极渗透测试的利基功能&#xff0c;以及广泛的开关&#xff0c;包括数据库指纹识别、从数据库中获取数据、访问底层文件系…

悟空crm安装搭建 报错[0] RedisException in Redis.php line 56问题处理办法

相信很多朋友进行安装悟空crm的时候 提示错误&#xff1a; [0] RedisException in Redis.php line 56 Connection refused 不知道怎么样处理是吧~~~ $this->options array_merge($this->options, $options);}# redis 密码$password config(cache.password);if (!empty…

1024特别剪辑: 使用Python Turtle 库绘制一棵随机生成的树

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

【excel技巧】excel单元格内如何换行?

Excel表格&#xff0c;在制作完成之后&#xff0c;在输入数据的时候&#xff0c;总是会遇到内容长度太长导致无法全部显示或者破坏表格整体格式。几天分享4个单元格换行的方法给大家。 方法一&#xff1a; 首先我们先介绍一个&#xff0c;通过调整列宽的方式来达到显示全部内…

xcode The document “...“ could not be saved

Today when I tried to save a file on my project I get an error message saying: The document “nameOfFile.m” could not be saved. I tried reinstalling xcode but no luck. The file can be edited with other editors and I see the same behavior on all my project…