Actor 模型使我想到了王者荣耀对战伤害控制实现

前言

  • hello,大家好,我是 Lorin,一天在群里闲聊,一位朋友想让我分享一篇关于 Actor 的文章,于是它诞生了,下面我们来看看 Actor 是什么?
  • 一般来说,我们有两种策略来在并发线程中实现通信:共享内存和消息传递。大多数传统语言,并发线程之间的通信使用的都是共享内存,共享内存最大的问题就是竞争,我们可以使用锁来解决竞争问题,但处理各种锁的问题让人头痛不已。
  • 而 Actor 模型是一种基于消息模型,在 Actor 模型中,一切皆 Actor ,Actor 之间通过消息进行通信, 但每个 Actor 同一时间只能处理一条消息,保证每个 Actor 独占式操作,从而巧妙的实现无锁并发编程。下面我们来看看 Actor 模型是如何基于消息模型实现无锁并发编程。

Actor 模型

  • Actor 模型是一种并发编程模型,用于处理多线程和分布式系统中的并发问题。它将并发计算分解为独立的、可并行执行的"角色"(Actors),这些角色之间通过消息传递进行通信,从而实现高度并发和分布式计算。
  • Actor 模型的设计理念是将计算单元封装成独立的角色,每个角色都有自己的状态和行为,角色内部状态只能自己修改和维护,同时能够接收和发送消息。

基本概念

Actor 角色

  • Actor 是 Actor 模型的基本单元,代表了一个独立的计算实体。
  • 每个 Actor 有一个唯一的标识符(地址),用于标识和访问该 Actor。
  • 每个 Actor 有自己的状态和行为,但不共享状态,状态由自己维护和修改。

Mailbox(邮箱)

  • 每个 Actor 都有一个 Mailbox(邮箱)用于接收其它 Actor 发送的数据,并等待接收者进行处理。

消息传递

  • 在 Actor 模型中通信通过消息传递实现,一个 Actor 可以向另外一个 Actor 发送消息,接受到消息后接收者会进行一些处理,比如进行一些计算或改变自己状态,但接受到的消息不一定会立即处理而是按照消息接收顺序进行异步处理。

图示

特点

  • 并发性: 每个 Actor 都是独立执行的,可以在不同的线程或进程中并行运行,从而实现高度并发。
  • 解耦性: Actors 之间的通信是松散耦合的,它们不共享状态,只通过消息交互。降低了系统的复杂性,使得系统更容易扩展和维护。
  • 容错性: 由于 Actors 之间相互独立,一个 Actor 的故障不会影响其他 Actors,从而提高了系统的容错性。
  • 分布式: Actor 模型天生支持分布式计算,因为 Actors 可以在不同的节点上运行,通过网络进行消息传递。

适用场景

  • 共享内存模型更适合高吞吐量的、要求低延迟的订单处理场景,因为它允许直接访问共享状态。
  • 而 Actor 模型更适用于需要隔离和异步处理的问题,比如游戏对战,异步通知,电信公司所有基站状态维护管理等场景,而不是高度优化的事务性操作,特别是在需要维护大量共享状态的情况下。
  • 实际使用中大多数场景会混合两种模式使用,比如一些电子商务系统,例如使用共享内存来管理订单的状态和库存,而使用 Actor 模型来处理异步通知、通信和与用户的交互。这种混合使用的方式可以根据具体需求来灵活选择合适的模型。

使用示例

  • 使用 Actor 实现简单双人对战游戏

版本及依赖

  • 版本:Jdk8
  • 依赖
Java 复制代码
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_3</artifactId>
            <version>2.8.5</version>
        </dependency>

实现

  • 创建一个表示玩家的 Actor
Java 复制代码
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;

public class PlayerActor extends AbstractActor {
    private String playerName;
    private int score;
    private ActorRef opponent;

    public PlayerActor(String playerName) {
        this.playerName = playerName;
        this.score = 0;
    }

    public static Props props(String playerName) {
        return Props.create(PlayerActor.class, playerName);
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .match(Attack.class, this::handleAttack)
            .match(Defend.class, this::handleDefend)
            .match(ReportScore.class, this::handleReportScore)
            .match(InitOpponent.class, this::handleInitOpponent)
            .build();
    }

    private void handleAttack(Attack attack) {
        opponent.tell(new Defend(attack.getDamage()), getSelf());
    }

    private void handleDefend(Defend defend) {
        int damage = defend.getDamage();
        score += damage;
        System.out.println(playerName + " defends against an attack and scores " + damage + " points.");
    }

    private void handleReportScore(ReportScore reportScore) {
        getSender().tell(new ScoreResponse(playerName, score), getSelf());
    }

    private void handleInitOpponent(InitOpponent initOpponent) {
        opponent = initOpponent.getOpponent();
    }

    public static class Attack {
        private final int damage;

        public Attack(int damage) {
            this.damage = damage;
        }

        public int getDamage() {
            return damage;
        }
    }

    public static class Defend {
        private final int damage;

        public Defend(int damage) {
            this.damage = damage;
        }

        public int getDamage() {
            return damage;
        }
    }

    public static class ReportScore {
    }

    public static class InitOpponent {
        private final ActorRef opponent;

        public InitOpponent(ActorRef opponent) {
            this.opponent = opponent;
        }

        public ActorRef getOpponent() {
            return opponent;
        }
    }

    public static class ScoreResponse {
        private final String playerName;
        private final int score;

        public ScoreResponse(String playerName, int score) {
            this.playerName = playerName;
            this.score = score;
        }

        public String getPlayerName() {
            return playerName;
        }

        public int getScore() {
            return score;
        }
    }
}
  • 创建一个游戏主控制 Actor,用于初始化和协调玩家
Java 复制代码
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;

public class GameController extends AbstractActor {
    private final ActorRef player1;
    private final ActorRef player2;

    public GameController(ActorRef player1, ActorRef player2) {
        this.player1 = player1;
        this.player2 = player2;

        // 初始化玩家的对手
        player1.tell(new PlayerActor.InitOpponent(player2), getSelf());
        player2.tell(new PlayerActor.InitOpponent(player1), getSelf());
    }

    public static Props props(ActorRef player1, ActorRef player2) {
        return Props.create(GameController.class, player1, player2);
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .match(PlayerActor.ScoreResponse.class, this::handleScoreResponse)
            .match(PlayerActor.Attack.class, this::handleAttack)
            .build();
    }

    private void handleScoreResponse(PlayerActor.ScoreResponse scoreResponse) {
        System.out.println(scoreResponse.getPlayerName() + " has a score of " + scoreResponse.getScore() + " points.");
    }

    private void handleAttack(PlayerActor.Attack attack) {
        // 随机选择一个玩家发起攻击
        if (Math.random() < 0.5) {
            player1.tell(attack, getSelf());
        } else {
            player2.tell(attack, getSelf());
        }
    }
}
  • 创建一个启动器来启动游戏
Java 复制代码
import akka.actor.ActorRef;
import akka.actor.ActorSystem;

public class GameLauncher {
    public static void main(String[] args) {
        ActorSystem system = ActorSystem.create("GameSystem");

        // 创建两个玩家
        ActorRef player1 = system.actorOf(PlayerActor.props("Player 1"));
        ActorRef player2 = system.actorOf(PlayerActor.props("Player 2"));

        // 创建游戏控制器
        ActorRef gameController = system.actorOf(GameController.props(player1, player2));

        // 发起攻击
        gameController.tell(new PlayerActor.Attack(10), ActorRef.noSender());
    }
}

// 运行结果
Player 2 defends against an attack and scores 5 points.
  • 上面示例是一个简单实现,实际项目场景中更加复杂,比如涉及血量、道具、蓝量等等。也许王者荣耀中对战的伤害控制就是基于 Actor 模型实现。
相关推荐
中国胖子风清扬9 分钟前
Rust 日志库完全指南:从入门到精通
spring boot·后端·rust·学习方法·logback
玉衡子13 分钟前
MySQL基础架构全面解析
数据库·后端
快乐肚皮14 分钟前
fencing token机制
java·fencing token
程序新视界15 分钟前
在职场,尽量不要成为这样的“人才”
面试·求职
叶落阁主24 分钟前
Neovim 插件 i18n.nvim 介绍
java·vue.js·vim
渣哥24 分钟前
让集合线程安全的几种靠谱方法
java
郭京京26 分钟前
goweb内置的 net/http 包
后端·go
dylan_QAQ27 分钟前
Java转Go全过程06-工程管理
java·后端·go
小奋斗28 分钟前
以Chrome 为代表的浏览器架构详解
面试·程序员
用户40993225021230 分钟前
如何用FastAPI玩转多模块测试与异步任务,让代码不再“闹脾气”?
后端·ai编程·trae