- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
上一篇博客中MyRpc框架实现了基本的点对点rpc通信功能。而在这篇博客中我们需要实现MyRpc的集群间rpc通信功能.
上篇博客的点对点rpc通信实现中,客户端和服务端的ip地址和端口都是固定配置死的。而通常为了提升服务总负载,客户端和服务端都是以集群的方式部署的(水平拓展),客户端和服务端的节点都不止1个.
集群条件下出现了很多新的问题需要解决:
MyRpc目前支持使用zookeeper作为注册中心。 zookeeper作为一个高性能的分布式协调器,存储的数据以ZNode节点树的形式存在。ZNode节点有两种属性,有序/无序,持久/临时.
MyRpc的zookeeper节点结构图 。
注册中心接口 。
/**
* 注册中心的抽象
* */
public interface Registry {
/**
* 服务注册
* */
void doRegistry(ServiceInfo serviceInfo);
/**
* 服务发现
* */
List<ServiceInfo> discovery(String serviceName);
}
zookeeper注册中心实现(原始的zk客户端) 。
/**
* 简易的zk注册中心(原始的zk客户端很多地方都需要用户去处理异常,但为了更简单的展示zk注册中心的使用,基本上没有处理这些异常情况)
* */
public class ZookeeperRegistry implements Registry{
private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
private final ZooKeeper zooKeeper;
private final ConcurrentHashMap<String,List<ServiceInfo>> serviceInfoCacheMap = new ConcurrentHashMap<>();
public ZookeeperRegistry(String zkServerAddress) {
try {
this.zooKeeper = new ZooKeeper(zkServerAddress,2000, event -> {});
// 确保root节点是一定存在的
createPersistentNode(MyRpcRegistryConstants.BASE_PATH);
} catch (Exception e) {
throw new MyRpcException("init zkClient error",e);
}
}
@Override
public void doRegistry(ServiceInfo serviceInfo) {
// 先创建永久的服务名节点
createServiceNameNode(serviceInfo.getServiceName());
// 再创建临时的providerInfo节点
createProviderInfoNode(serviceInfo);
}
@Override
public List<ServiceInfo> discovery(String serviceName) {
return serviceInfoCacheMap.computeIfAbsent(serviceName,(key)-> findProviderInfoList(serviceName));
}
private String getServiceNameNodePath(String serviceName){
return MyRpcRegistryConstants.BASE_PATH + "/" + serviceName;
}
// ================================ zk工具方法 ==================================
private void createServiceNameNode(String serviceName){
try {
String serviceNameNodePath = getServiceNameNodePath(serviceName);
// 服务名节点是永久节点
createPersistentNode(serviceNameNodePath);
logger.info("createServiceNameNode success! serviceNameNodePath={}",serviceNameNodePath);
} catch (Exception e) {
throw new MyRpcException("createServiceNameNode error",e);
}
}
private void createProviderInfoNode(ServiceInfo serviceInfo){
try {
String serviceNameNodePath = getServiceNameNodePath(serviceInfo.getServiceName());
// 子节点用一个uuid做path防重复
String providerInfoNodePath = serviceNameNodePath + "/" + UUID.randomUUID();
String providerInfoJsonStr = JsonUtil.obj2Str(serviceInfo);
// providerInfo节点是临时节点(如果节点宕机了,zk的连接断开一段时间后,临时节点会被自动删除)
zooKeeper.create(providerInfoNodePath, providerInfoJsonStr.getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
logger.info("createProviderInfoNode success! path={}",providerInfoNodePath);
} catch (Exception e) {
throw new MyRpcException("createProviderInfoNode error",e);
}
}
private void createPersistentNode(String path){
try {
if (zooKeeper.exists(path, false) == null) {
// 服务名节点是永久节点
zooKeeper.create(path, "".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}catch (Exception e){
throw new MyRpcException("createPersistentNode error",e);
}
}
private List<ServiceInfo> findProviderInfoList(String serviceName){
String serviceNameNodePath = getServiceNameNodePath(serviceName);
List<ServiceInfo> serviceInfoList = new ArrayList<>();
try {
List<String> providerInfoPathList = zooKeeper.getChildren(serviceNameNodePath, new ZookeeperListener(serviceNameNodePath));
for(String providerInfoPath : providerInfoPathList){
try{
String fullProviderInfoPath = serviceNameNodePath + "/" + providerInfoPath;
byte[] data = zooKeeper.getData(fullProviderInfoPath,false,null);
String jsonStr = new String(data,StandardCharsets.UTF_8);
ServiceInfo serviceInfo = JsonUtil.json2Obj(jsonStr,ServiceInfo.class);
serviceInfoList.add(serviceInfo);
}catch (Exception e){
logger.error("findProviderInfoList getData error",e);
}
}
logger.info("findProviderInfoList={}",JsonUtil.obj2Str(serviceInfoList));
return serviceInfoList;
} catch (Exception e) {
throw new MyRpcException("findProviderInfoList error",e);
}
}
private class ZookeeperListener implements Watcher{
private final String path;
private final String serviceName;
public ZookeeperListener(String serviceName) {
this.path = getServiceNameNodePath(serviceName);
this.serviceName = serviceName;
}
@Override
public void process(WatchedEvent event) {
logger.info("ZookeeperListener process! path={}",path);
try {
// 刷新缓存
List<ServiceInfo> serviceInfoList = findProviderInfoList(path);
serviceInfoCacheMap.put(serviceName,serviceInfoList);
} catch (Exception e) {
logger.error("ZookeeperListener getChildren error! path={}",path,e);
}
}
}
}
zookeeper注册中心实现(curator客户端,通过自动重试等操作解决了原生客户端的一些坑) 。
public class ZkCuratorRegistry implements Registry {
private static final Logger logger = LoggerFactory.getLogger(ZkCuratorRegistry.class);
private CuratorFramework curatorZkClient;
private final ConcurrentHashMap<String, List<ServiceInfo>> serviceInfoCacheMap = new ConcurrentHashMap<>();
private static ConcurrentHashMap<String, PathChildrenCache> nodeCacheMap = new ConcurrentHashMap<>();
public ZkCuratorRegistry(String zkServerAddress) {
try {
this.curatorZkClient = CuratorFrameworkFactory.newClient(zkServerAddress, new ExponentialBackoffRetry(3000, 1));
this.curatorZkClient.start();
} catch (Exception e) {
throw new MyRpcException("init zkClient error", e);
}
}
@Override
public void doRegistry(ServiceInfo serviceInfo) {
// 先创建永久的服务名节点
createServiceNameNode(serviceInfo.getServiceName());
// 再创建临时的providerInfo节点
createProviderInfoNode(serviceInfo);
}
@Override
public List<ServiceInfo> discovery(String serviceName) {
return this.serviceInfoCacheMap.computeIfAbsent(serviceName,(key)->{
List<ServiceInfo> serviceInfoList = findProviderInfoList(serviceName);
// 创建对子节点的监听
String serviceNodePath = getServiceNameNodePath(serviceName);
PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorZkClient, serviceNodePath, true);
try {
pathChildrenCache.start();
nodeCacheMap.put(serviceName,pathChildrenCache);
pathChildrenCache.getListenable().addListener(new ZkCuratorListener(serviceName));
} catch (Exception e) {
throw new MyRpcException("PathChildrenCache start error!",e);
}
return serviceInfoList;
});
}
private void createServiceNameNode(String serviceName) {
try {
String serviceNameNodePath = getServiceNameNodePath(serviceName);
// 服务名节点是永久节点
if (curatorZkClient.checkExists().forPath(serviceNameNodePath) == null) {
curatorZkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(serviceNameNodePath);
}
logger.info("createServiceNameNode success! serviceNameNodePath={}", serviceNameNodePath);
} catch (Exception e) {
throw new MyRpcException("createServiceNameNode error", e);
}
}
private void createProviderInfoNode(ServiceInfo serviceInfo) {
try {
String serviceNameNodePath = getServiceNameNodePath(serviceInfo.getServiceName());
// 子节点用一个uuid做path防重复
String providerInfoNodePath = serviceNameNodePath + "/" + UUID.randomUUID();
String providerInfoJsonStr = JsonUtil.obj2Str(serviceInfo);
// providerInfo节点是临时节点(如果节点宕机了,zk的连接断开一段时间后,临时节点会被自动删除)
curatorZkClient.create()
.withMode(CreateMode.EPHEMERAL)
.forPath(providerInfoNodePath, providerInfoJsonStr.getBytes(StandardCharsets.UTF_8));
logger.info("createProviderInfoNode success! path={}", providerInfoNodePath);
} catch (Exception e) {
throw new MyRpcException("createProviderInfoNode error", e);
}
}
private String getServiceNameNodePath(String serviceName) {
return MyRpcRegistryConstants.BASE_PATH + "/" + serviceName;
}
private List<ServiceInfo> findProviderInfoList(String serviceName) {
String serviceNameNodePath = getServiceNameNodePath(serviceName);
try {
List<String> providerInfoPathList = curatorZkClient.getChildren().forPath(serviceNameNodePath);
List<ServiceInfo> serviceInfoList = new ArrayList<>();
for(String providerInfoPath : providerInfoPathList){
try{
String fullProviderInfoPath = serviceNameNodePath + "/" + providerInfoPath;
byte[] data = curatorZkClient.getData().forPath(fullProviderInfoPath);
String jsonStr = new String(data,StandardCharsets.UTF_8);
ServiceInfo serviceInfo = JsonUtil.json2Obj(jsonStr,ServiceInfo.class);
serviceInfoList.add(serviceInfo);
}catch (Exception e){
logger.error("findProviderInfoList getData error",e);
}
}
logger.info("findProviderInfoList={}",JsonUtil.obj2Str(serviceInfoList));
return serviceInfoList;
} catch (Exception e) {
throw new MyRpcException("findProviderInfoList error",e);
}
}
private class ZkCuratorListener implements PathChildrenCacheListener {
private final String serviceName;
public ZkCuratorListener(String serviceName) {
this.serviceName = serviceName;
}
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) {
logger.info("ZookeeperListener process! serviceName={}",serviceName);
try {
// 刷新缓存
List<ServiceInfo> serviceInfoList = findProviderInfoList(serviceName);
serviceInfoCacheMap.put(serviceName,serviceInfoList);
} catch (Exception e) {
logger.error("ZookeeperListener getChildren error! serviceName={}",serviceName,e);
}
}
}
}
现在客户端已经能通过服务发现得到实时的provider集合了,那么客户端发起请求时应该如何决定向哪个provider发起请求以实现provider侧的负载均衡呢?
负载均衡接口 。
/**
* 负载均衡选择器
* */
public interface LoadBalance {
ServiceInfo select(List<ServiceInfo> serviceInfoList);
}
随机负载均衡 。
/**
* 无权重,纯随机的负载均衡选择器
* */
public class RandomLoadBalance implements LoadBalance{
@Override
public ServiceInfo select(List<ServiceInfo> serviceInfoList) {
int selectedIndex = ThreadLocalRandom.current().nextInt(serviceInfoList.size());
return serviceInfoList.get(selectedIndex);
}
}
/**
* 无权重的轮训负载均衡(后续增加带权重的轮训)
* */
public class SimpleRoundRobinBalance implements LoadBalance{
private final AtomicInteger count = new AtomicInteger();
@Override
public ServiceInfo select(List<ServiceInfo> serviceInfoList) {
if(serviceInfoList.isEmpty()){
throw new MyRpcException("serviceInfoList is empty!");
}
// 考虑一下溢出,取绝对值
int selectedIndex = Math.abs(count.getAndIncrement());
return serviceInfoList.get(selectedIndex % serviceInfoList.size());
}
}
由于需要通过网络发起rpc调用,比起本地调用很容易因为网络波动、远端机器故障等原因而导致调用失败。 客户端有时希望能通过重试等方式屏蔽掉可能出现的偶发错误,尽可能的保证rpc请求的成功率,最好rpc框架能解决这个问题。但另一方面,能够安全重试的基础是下游服务能够做到幂等,否则重复的请求会带来意想不到的后果,而不幂等的下游服务只能至多调用一次。 因此rpc框架需要能允许用户不同的服务可以有不同的集群服务调用方式,这样幂等的服务可以配置成可自动重试N次的failover调用、只能调用1次的fast-fail调用或者广播调用等等方式.
MyRpc的Invoker接口用于抽象上述的不同集群调用方式,并简单的实现了failover和fast-fail等多种调用方式(参考dubbo).
public interface InvokerCallable {
RpcResponse invoke(NettyClient nettyClient);
}
/**
* 不同的集群调用方式
* */
public interface Invoker {
RpcResponse invoke(InvokerCallable callable, String serviceName,
Registry registry, LoadBalance loadBalance);
}
/**
* 快速失败,无论成功与否调用1次就返回
* */
public class FastFailInvoker implements Invoker {
private static final Logger logger = LoggerFactory.getLogger(FastFailInvoker.class);
@Override
public RpcResponse invoke(InvokerCallable callable, String serviceName,
Registry registry, LoadBalance loadBalance) {
List<ServiceInfo> serviceInfoList = registry.discovery(serviceName);
logger.debug("serviceInfoList.size={},serviceInfoList={}",serviceInfoList.size(), JsonUtil.obj2Str(serviceInfoList));
NettyClient nettyClient = InvokerUtil.getTargetClient(serviceInfoList,loadBalance);
logger.info("ClientDynamicProxy getTargetClient={}", nettyClient);
// fast-fail,简单的调用一次就行,有错误就直接向上抛
return callable.invoke(nettyClient);
}
}
/**
* 故障转移调用(如果调用出现了错误,则重试指定次数)
* 1 如果重试过程中成功了,则快读返回
* 2 如果重试了指定次数后还是没成功,则抛出异常
* */
public class FailoverInvoker implements Invoker {
private static final Logger logger = LoggerFactory.getLogger(FailoverInvoker.class);
private final int defaultRetryCount = 2;
private final int retryCount;
public FailoverInvoker() {
this.retryCount = defaultRetryCount;
}
public FailoverInvoker(int retryCount) {
this.retryCount = Math.max(retryCount,1);
}
@Override
public RpcResponse invoke(InvokerCallable callable, String serviceName, Registry registry, LoadBalance loadBalance) {
MyRpcException myRpcException = null;
for(int i=0; i<retryCount; i++){
List<ServiceInfo> serviceInfoList = registry.discovery(serviceName);
logger.debug("serviceInfoList.size={},serviceInfoList={}",serviceInfoList.size(), JsonUtil.obj2Str(serviceInfoList));
NettyClient nettyClient = InvokerUtil.getTargetClient(serviceInfoList,loadBalance);
logger.info("ClientDynamicProxy getTargetClient={}", nettyClient);
try {
RpcResponse rpcResponse = callable.invoke(nettyClient);
if(myRpcException != null){
// 虽然最终重试成功了,但是之前请求失败过
logger.warn("FailRetryInvoker finally success, but there have been failed providers");
}
return rpcResponse;
}catch (Exception e){
myRpcException = new MyRpcException(e);
logger.warn("FailRetryInvoker callable.invoke error",e);
}
}
// 走到这里说明经过了retryCount次重试依然不成功,myRpcException一定不为null
throw myRpcException;
}
}
/**
* 客户端动态代理
* */
public class ClientDynamicProxy implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(ClientDynamicProxy.class);
private final Registry registry;
private final LoadBalance loadBalance;
private final Invoker invoker;
public ClientDynamicProxy(Registry registry, LoadBalance loadBalance, Invoker invoker) {
this.registry = registry;
this.loadBalance = loadBalance;
this.invoker = invoker;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Tuple<Object,Boolean> localMethodResult = processLocalMethod(proxy,method,args);
if(localMethodResult.getRight()){
// right为true,代表是本地方法,返回toString等对象自带方法的执行结果,不发起rpc调用
return localMethodResult.getLeft();
}
logger.debug("ClientDynamicProxy before: methodName=" + method.getName());
String serviceName = method.getDeclaringClass().getName();
// 构造请求和协议头
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setInterfaceName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParameterClasses(method.getParameterTypes());
rpcRequest.setParams(args);
MessageHeader messageHeader = new MessageHeader();
messageHeader.setMessageFlag(MessageFlagEnums.REQUEST.getCode());
messageHeader.setTwoWayFlag(false);
messageHeader.setEventFlag(true);
messageHeader.setSerializeType(GlobalConfig.messageSerializeType.getCode());
messageHeader.setResponseStatus((byte)'a');
messageHeader.setMessageId(rpcRequest.getMessageId());
logger.debug("ClientDynamicProxy rpcRequest={}", JsonUtil.obj2Str(rpcRequest));
RpcResponse rpcResponse = this.invoker.invoke((nettyClient)->{
Channel channel = nettyClient.getChannel();
// 将netty的异步转为同步,参考dubbo DefaultFuture
DefaultFuture<RpcResponse> newDefaultFuture = DefaultFutureManager.createNewFuture(channel,rpcRequest);
try {
nettyClient.send(new MessageProtocol<>(messageHeader,rpcRequest));
// 调用方阻塞在这里
return newDefaultFuture.get();
} catch (Exception e) {
throw new MyRpcException("InvokerCallable error!",e);
}
},serviceName,registry,loadBalance);
logger.debug("ClientDynamicProxy defaultFuture.get() rpcResponse={}",rpcResponse);
return processRpcResponse(rpcResponse);
}
/**
* 处理本地方法
* @return tuple.right 标识是否是本地方法, true是
* */
private Tuple<Object,Boolean> processLocalMethod(Object proxy, Method method, Object[] args) throws Exception {
// 处理toString等对象自带方法,不发起rpc调用
if (method.getDeclaringClass() == Object.class) {
return new Tuple<>(method.invoke(proxy, args),true);
}
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
if ("toString".equals(methodName)) {
return new Tuple<>(proxy.toString(),true);
} else if ("hashCode".equals(methodName)) {
return new Tuple<>(proxy.hashCode(),true);
}
} else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
return new Tuple<>(proxy.equals(args[0]),true);
}
// 返回null标识非本地方法,需要进行rpc调用
return new Tuple<>(null,false);
}
private Object processRpcResponse(RpcResponse rpcResponse){
if(rpcResponse.getExceptionValue() == null){
// 没有异常,return正常的返回值
return rpcResponse.getReturnValue();
}else{
// 有异常,往外抛出去
throw new MyRpcRemotingException(rpcResponse.getExceptionValue());
}
}
}
客户端发起请求后,可能由于网络原因,可能由于服务端负载过大等原因而迟迟无法收到回复。 出于性能或者自身业务的考虑,客户端不能无限制的等待下去,因此rpc框架需要能允许客户端设置请求的超时时间。在一定的时间内如果无法收到响应则需要抛出超时异常,令调用者及时的感知到问题.
在客户端侧DefaultFuture.get方法,指定超时时间是可以做到这一点的。 但其依赖底层操作系统的定时任务机制,虽然超时时间的精度很高(nanos级别),但在高并发场景下性能不如时间轮。 具体原理可以参考我之前的博客: 时间轮TimeWheel工作原理解析 。
MyRpc参考dubbo,引入时间轮来实现客户端设置请求超时时间的功能.
public class DefaultFutureManager {
private static final Logger logger = LoggerFactory.getLogger(DefaultFutureManager.class);
public static final Map<Long,DefaultFuture> DEFAULT_FUTURE_CACHE = new ConcurrentHashMap<>();
public static final HashedWheelTimer TIMER = new HashedWheelTimer();
public static void received(RpcResponse rpcResponse){
Long messageId = rpcResponse.getMessageId();
logger.debug("received rpcResponse={},DEFAULT_FUTURE_CACHE={}",rpcResponse,DEFAULT_FUTURE_CACHE);
DefaultFuture defaultFuture = DEFAULT_FUTURE_CACHE.remove(messageId);
if(defaultFuture != null){
logger.debug("remove defaultFuture success");
if(rpcResponse.getExceptionValue() != null){
// 异常处理
defaultFuture.completeExceptionally(rpcResponse.getExceptionValue());
}else{
// 正常返回
defaultFuture.complete(rpcResponse);
}
}else{
// 可能超时了,接到响应前已经remove掉了这个future(超时和实际接到请求都会调用received方法)
logger.debug("remove defaultFuture fail");
}
}
public static DefaultFuture createNewFuture(Channel channel, RpcRequest rpcRequest){
DefaultFuture defaultFuture = new DefaultFuture(channel,rpcRequest);
// 增加超时处理的逻辑
newTimeoutCheck(defaultFuture);
return defaultFuture;
}
public static DefaultFuture getFuture(long messageId){
return DEFAULT_FUTURE_CACHE.get(messageId);
}
/**
* 增加请求超时的检查任务
* */
public static void newTimeoutCheck(DefaultFuture defaultFuture){
TimeoutCheckTask timeoutCheckTask = new TimeoutCheckTask(defaultFuture.getMessageId());
TIMER.newTimeout(timeoutCheckTask, defaultFuture.getTimeout(), TimeUnit.MILLISECONDS);
}
}
public class TimeoutCheckTask implements TimerTask {
private final long messageId;
public TimeoutCheckTask(long messageId) {
this.messageId = messageId;
}
@Override
public void run(Timeout timeout) {
DefaultFuture defaultFuture = DefaultFutureManager.getFuture(this.messageId);
if(defaultFuture == null || defaultFuture.isDone()){
// 请求已经在超时前返回,处理过了,直接返回即可
return;
}
// 构造超时的响应
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setMessageId(this.messageId);
rpcResponse.setExceptionValue(new MyRpcTimeoutException(
"request timeout:" + defaultFuture.getTimeout() + " channel=" + defaultFuture.getChannel()));
DefaultFutureManager.received(rpcResponse);
}
}
最后此篇关于自己动手实现rpc框架(二)实现集群间rpc通信的文章就讲到这里了,如果你想了解更多关于自己动手实现rpc框架(二)实现集群间rpc通信的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!