Dubbogo 详解

Dubbogo 详解

简介

dubbo功能很强大的微服务开发框架,支持多种通信协议,并具有流量治理的功能。

dubbo在有了大转变,拥抱了云原生,从哪些方面可以体现呢?

  1. 推出了自己的Trip协议
  2. 修复了服务发现的级别,之前是方法级别,现在是服务级别
  3. 提供了服务发现,流量控制和XDS的结合

当然,dubbo还是那个dubbo,原来的配置都有,并且支持跨语言通信,并且方便我们开发,提供了cli的工具来使用,所以我们的通信变成了下面的样子

  • 先写IDL
  • 实现对应的服务,调用对应的接口。

dubbo有对应的示例代码:

https://github.com/apache/dubbo-go-samples

dubbo的架构和分层

我想引用官网上的图来清晰的说明,dubbo的抽象是很重要的。

dubbo中有几个重要的接口和对象

  • invoker:表示一个可调用的抽象对象,不管是从loadBalance,还是从cluster,还是dictionary,都表示的是一个invoker。
  • Invocation:调用的上下文。通过它可以调用此次调用时候的参数名字,参数类型,返回值等等一系列的数据

具体的可以参考官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/code-architecture/
在这里插入图片描述

在这里插入图片描述

这是从dubbo的官网上copy的图,源码的分析按照这个图来看,很清晰,可能会有一点点的不一样,但这也没太大的问题。

在这里插入图片描述

分为服务端和客户端:

服务端:构建代理对象,接受服务端的请求。

客户端:构建代理对象,请求服务端。

这里需要处理几个问题

  1. 怎么在服务端和客户端调用的基础上,将dubbo这一套东西加上去

    每个通信协议在dubbo中都有自己的通信协议的实现,当让他们也是一个invoker,它作为baseInvoker,在此基础上增加各种各样的invoker(cluster,loadbalance,filter等)。

    对于客户端而言,也有baseInvoker,客户端而言,只有filter。

  2. go中是没有动态代理的概念的,所以,对于客户端来说,怎么生成代理对象。

    代理对象不能再运行的时候生成,那可以提早生成。

    dubbo提供IDL的工具实现了此功能,提早已经生成了好了代理对象,用来做真正的调用。

    并且生成了一个对应的ClientImpl,它是一个结构体,结构体中的属性是对应的方法,此时就可以利用GO的反射来动态的修改属性,这样在构建的时候就做到了。

服务端创建一个exporter开始

code:

type GreeterProvider struct {
	api.UnimplementedGreeterServer
}

func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {
	logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)
	return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
}

// export DUBBO_GO_CONFIG_PATH= PATH_TO_SAMPLES/direct/go-server/conf/dubbogo.yml
func main() {
	config.SetProviderService(&GreeterProvider{})
	if err := config.Load(); err != nil {
		panic(err)
	}
	select {}
}

将对应的实现类提供给dubbo,并且启动。

  1. 注册到dubbo中

    在整个dubbo没有启动之前,这里的注册只是放在一个map中,需要保证key是唯一。

  2. 启动

    启动分为五步

    • 初始化config
    • 初始化provider
    • 初始化consumer
    • 初始化shutdown

初始化好此provider的配置,配置叫做ServiceConfig

调用Export来做真正的服务创建操作。

会通过具体的通信协议创建对应的invoker,并且结合filter。并且将服务注册到注册中心。

总体流程是这样,现在回答几个问题:

  1. dubbo中是怎么调用到真正的provider,也就是说dubbo是怎么和provider关联的。
  2. Invoker(调用者)是怎么构建的?

问题一:

dubbo是通过反射来调用的。

原理如下:

dubbo的trip协议是grpc的基础上改进的,所以要从这一点入手,从grpc我们知道,在启动的时候要注册相应的服务,在dubbo这里,从生产的pb文件中入手
在这里插入图片描述

会将此服务描述信息告诉grpc,当调用来了之后,就会调用到对应的方法中。这里我们以_Greeter_SayHello_Handler为例,在此方法中,会构建invocation,然后在调用dubbo构建的invoker对象,就完成连接了。

在这里插入图片描述

代理对象是什么?

代理对象就是dubbo生成的invoker,在构建好invoker之后,会将它作为属性放在provider中,之后在客户端调用的时候可以通过反射来调用对应的方法。

dubbo为grpc的服务规定了接口。通过此接口就可以获取代理对象,获取服务的描述性息
在这里插入图片描述

并且官网提供了可拓展点和完整的调用说明:
https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/service-invocation/
在这里插入图片描述

在什么时候注册grpc服务,并且设置代理对象的?

在构建好invoker对象之后,会调用XXX_SetProxyImpl方法,设置值。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

问题 二:

invoker是dubbo中很重要的概念,表示的是一个可调用者,通过这个概念,dubbo高度抽象了服务,
在这里插入图片描述

官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

invoker表示可用的服务,不管是loadbalance、router、director等,都是一个可用的invoker。

在抽象的invoker都有基础的实现,剩下的filter,cluster都是基于此继续操作的。我们来看invoker的构建过程

在准备好服务方的配置之后,就开始挨个启动服务了,每个服务的定义信息叫做ServiceConfig,调用Export来初始化此服务,并且注册到注册中心。

我们知道dubbo中服务的定义信息传递全都靠url,在Export中,主要有下面的几个步骤

  1. 获取注册中心,创建代理工厂(用于代理对象的创建)
  2. 构建url
  3. 通过不同的通信协议构建不同的Invoker,并且启动底层具体通信协议对应的服务器。
  4. 服务发布到注册中心

这里我们只说Invoker的构建。对应到代理里面如下:

	invoker := proxyFactory.GetInvoker(ivkURL) // 通过代理工厂创建对应的代理类
	exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) // 从extension中获取对应的底层通信协议来构建invoker

先看代理工厂如何创建Invoker

在这里插入图片描述

基础的调用就是ProxyInvoker,等请求来了之后,通过一系列filter,会走到这里,通过反射来调用具体的方法。

不同的通信协议如何构建invoker

在这里插入图片描述

extension是一个拓展点,也是通过它逐渐的让dubbo丰满起来。

filter对应的wrapper是ProtocolFilterWrapper,通过它基于ProxyInvoker来构建最终的Invoker,最后通过最终的通信协议的来构建最终的Invoker,从而启动服务器,注册服务。

在dubbo的protocol包下面列举出了支持的通信协议

在这里插入图片描述

我们来查看dubbo3种trip的通信协议

上面已经介绍过了,主要是确定序列化类型,设置invoker代理。启动服务器

还有一个问题

最终是怎么通过反射调用的,并且何时设置的反射呢

直接去ProxyInvoker中查看invoke方法

在这里插入图片描述

这里对反射要说明一下,反射调用的时候需要确定参数的个数,结合实际,参数个数就一个或者两个,其中必须用context,剩下的要么是无参,要么就一个request对象。

查看serviceMap.Register方法可知,在注册的时候会解析此结构体中的方法,并且设置封装为Service对象

客户端创建一个Ref开始

客户端的构建思路和上面的差不多,不过多了注册中心,cluster的构建。

还是从几个问题开始

  1. 代理对象的创建和关联
  2. invoker对象的构建流程

整个流程从ReferenceConfig.Refer方法开始

代理对象的创建

按照dubbo的逻辑,对象的创建必须去proxyFactory中,但这里是整个filter,cluster构建好了之后,在基于此构建代理对象,因为这里的代理对象是最终给用户侧来调用的,所以,肯定是放在最后一步的。

最终的入口在DefaultProxyFactory.GetProxy中,此方法中会创建Proxy对象

type Proxy struct {
	rpc         common.RPCService // 代理还要保留origin
	invoke      protocol.Invoker // 生成的invoke对象,这是代表整个dubbo服务的invoke,此invoke中包含了 cluster,load等等流量治理的功能
	callback    interface{}
	attachments map[string]string
	implement   ImplementFunc // 动态函数,这是一个函数类型,会通过此函数来实现普通服务和dubbo服务的对接
	once        sync.Once
}

在这里插入图片描述

这里只是设置了proxy,并没有做方法绑定操作,真正的是在ReferenceConfig.Implement中实现的。他会调用到刚刚创建的proxy对象中的implement方法,从而给客户端动态绑定属性,达到调用dubbo服务的功能。这是重点,我们源码分析一下

源码:proxy.DefaultProxyImplementFunc

func DefaultProxyImplementFunc(p *Proxy, v common.RPCService) {
	// 开始反射操作
	valueOf := reflect.ValueOf(v)

	valueOfElem := valueOf.Elem()
	// 这是重点,最终会将客户端中的属性方法,都设置为此函数,然后在此函数里面做调用。
    // 当我们调用方法的时候,会调用到这里来,
	makeDubboCallProxy := func(methodName string, outs []reflect.Type) func(in []reflect.Value) []reflect.Value {
		return func(in []reflect.Value) []reflect.Value {
			var (
				err            error
				inv            *invocation_impl.RPCInvocation
				inIArr         []interface{}
				inVArr         []reflect.Value
				reply          reflect.Value
				replyEmptyFlag bool
			)
			if methodName == "Echo" {
				methodName = "$echo"
			}
			// 确定参数和返回值等信息
			if len(outs) == 2 { // return (reply, error)
				if outs[0].Kind() == reflect.Ptr {
					reply = reflect.New(outs[0].Elem())
				} else {
					reply = reflect.New(outs[0])
				}
			} else { // only return error
				replyEmptyFlag = true
			}

			start := 0
			end := len(in)
			invCtx := context.Background()
			// retrieve the context from the first argument if existed
			if end > 0 {
				if in[0].Type().String() == "context.Context" {
					if !in[0].IsNil() {
						// the user declared context as method's parameter
						invCtx = in[0].Interface().(context.Context)
					}
					start += 1
				}
			}

			if end-start <= 0 {
				inIArr = []interface{}{}
				inVArr = []reflect.Value{}
			} else if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 {
				inIArr = v
				inVArr = []reflect.Value{in[start]}
			} else {
				inIArr = make([]interface{}, end-start)
				inVArr = make([]reflect.Value, end-start)
				index := 0
				for i := start; i < end; i++ {
					inIArr[index] = in[i].Interface()
					inVArr[index] = in[i]
					index++
				}
			}
			
			inv = invocation_impl.NewRPCInvocationWithOptions(invocation_impl.WithMethodName(methodName),
				invocation_impl.WithArguments(inIArr),
				invocation_impl.WithCallBack(p.callback), invocation_impl.WithParameterValues(inVArr))
			if !replyEmptyFlag {
				inv.SetReply(reply.Interface()) // 这里的reply就是response
			}

			for k, value := range p.attachments {
				inv.SetAttachment(k, value)
			}

			// add user setAttachment. It is compatibility with previous versions.
			atm := invCtx.Value(constant.AttachmentKey)
			if m, ok := atm.(map[string]string); ok {
				for k, value := range m {
					inv.SetAttachment(k, value)
				}
			} else if m2, ok2 := atm.(map[string]interface{}); ok2 {
				// it is support to transfer map[string]interface{}. It refers to dubbo-java 2.7.
				for k, value := range m2 {
					inv.SetAttachment(k, value)
				}
			}
			// 调用服务,这里用的是闭包
			result := p.invoke.Invoke(invCtx, inv)
			err = result.Error()
			// cause is raw user level error
			cause := perrors.Cause(err)
			if err != nil {
				// if some error happened, it should be log some info in the separate file.
				if throwabler, ok := cause.(java_exception.Throwabler); ok {
					logger.Warnf("[CallProxy] invoke service throw exception: %v , stackTraceElements: %v", cause.Error(), throwabler.GetStackTrace())
				} else {
					// entire error is only for printing, do not return, because user would not want to deal with massive framework-level error message
					logger.Warnf("[CallProxy] received rpc err: %v", err)
				}
			} else {
				logger.Debugf("[CallProxy] received rpc result successfully: %s", result)
			}
			if len(outs) == 1 {
				return []reflect.Value{reflect.ValueOf(&cause).Elem()}
			}
			if len(outs) == 2 && outs[0].Kind() != reflect.Ptr {
				return []reflect.Value{reply.Elem(), reflect.ValueOf(&cause).Elem()}
			}
			return []reflect.Value{reply, reflect.ValueOf(&cause).Elem()}
		}
	}

	if err := refectAndMakeObjectFunc(valueOfElem, makeDubboCallProxy); err != nil {
		logger.Errorf("The type or combination type of RPCService %T must be a pointer of a struct. error is %s", v, err)
		return
	}
}

在这里插入图片描述

invoker的构建是什么样子?

对应的代码在ReferenceConfig.Refer

  1. 构建url
  2. 通过不同的通信协议构建不同的base invoker,对于trip来说就是DubboInvoker
  3. 基于此Invoker,增加filter功能
  4. 增加cluster
  5. 构建代理对象

DubboInvoker是什么样子

在这里插入图片描述

在这里插入图片描述

cluster

dubbo提供的集群容错的策略,cluster中的交互逻辑如下:

在这里插入图片描述

可以看到,整体的流程是获取所有的可用的invoker,然后通过路由,负载均衡,选择出一个invoker来做调用。

官网链接:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/cluster

支持的策略如下:

理解了上面的逻辑,这里的代码比较好理解。

在这里插入图片描述

failover

快速切换,规定了重试次数,超过之后就失败。

在这里插入图片描述

failfast

只调用一次。失败就失败了

在这里插入图片描述

failsafe

在这里插入图片描述

会打印错误日志,并且返回一个空对象回去

forking

在这里插入图片描述

并行调用(默认为2),主要有一个成功,就返回,规定超时时间(1s)就返回失败

broadcast

在这里插入图片描述

广播调用,等待所有的调用结束之后才返回

available

在这里插入图片描述

zoneaware

统一地区的优先访问,其实就是做load balance 之前增加了一层筛选

adaptivesvc

自适应的cluster,需要搭配p2c load balance 使用,这个比较复杂,需要搭配filter使用。之后出文章详解。

failback

失败恢复,将失败的请求放在队列里面,重试,但没有返回值

这里需要一个机制,重试机制的实现

在Java中是放在时间轮里面实现的,go用ticker实现定时触发,一秒一次。

默认重试3次,重试队列长度为100

重试队列

在这里插入图片描述

处理过程

在这里插入图片描述

在这里插入图片描述

当失败之后,就会将调用放在一个定时任务的队列里面,每隔一秒就会将所有的任务执行一次(任务的间隔必须大于5秒),如果失败之后,通过条件决定是否要放在队列中。

loadBalance

官网:https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/loadbalance

负载均衡从一堆可用的invoker中选一个出来。

consistenthashing

一致性hash,确定的入参,确定的提供者,适用于有状态请求。

但当invoker发生了变化,会重新计算hash分配。这里需要构建hash环,说到hash环就得有虚拟节点,虚拟节点是为了更好的分配平均,在这里每个物理节点会对应160个虚拟节点(默认)

在这里插入图片描述

用crc生成一个hashcode,从而生成hash环,每个方法对应一个hash环,并且hashcode发生了变化,就会重构hash环。下面的代码分为三步

hash环构建
在这里插入图片描述

每个物理节点有160个虚拟节点,并且用节点的ip+端口生成address,之后分为两次循环,外层40,内层4次,生成hashcode,并且保存虚拟节点和物理节点的映射关系。最后对生成的hash环做排序,方便之后的查找。

hash环查找

通过key生成hashcode,然后用找到第一个大于等于当前hash值的key,之后返回对应的物理节点

在这里插入图片描述

hash函数

好的函数很重要,可以让请求分配的更加的均匀。

在这里插入图片描述

这我看不懂了,看看GPT的回答吧

在这里插入图片描述

leastactive

最近最少活跃,需要配合filter继续当前节点的活跃情况,如果有多个相同情况的invoke。就通过权重来选择一个,如果还有相同的,就随机选择一个。

在这里插入图片描述

关于随机匹配,会产生一个总权重的随机值,占的比重大的为负数的概率低。

random

加权随机,按照权重做随机

在这里插入图片描述

ringhash

和服务网格相关,这里不做过多介绍

roundrobin

加权轮询,它的算法和Nginx平滑加权轮询算法,默认权重相同。

https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/#roundrobin
在这里插入图片描述

具体可看官网的解释,主要是为了在保证轮询的情况下,尽可能的分布均匀。

轮询算法需要保存上一次选择的节点的状态。

这里分为两步

选择节点

在这里插入图片描述

保存节点状态

在这里插入图片描述

p2c

自适应的算法。

这种算法需要结合filter来实现,之后在详细的说

dubbogo项目分层很清晰,没有像Java那样花里胡哨的代码,也没有线程池,时间轮等这些算法,好理解。


到这里就结束了。dubbo

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

20230723红米Redmi Note8Pro掉在水里的处理步骤

20230723红米Redmi Note8Pro掉在水里的处理步骤 2023/7/23 18:18 百度搜搜&#xff1a;小米手机进水 破音怎么处理 Redmi Note8Pro 6400万全场景四摄 液冷游戏芯 4500mAh长续航 NFC 18W快充 红外遥控 https://www.zhiliancy.com/a/q5podmr12.html 首页 / 热文 / 内容 小米喇叭…

【从删库到跑路】MySQL数据库的索引(一)——索引的结构(BTree B+Tree Hash),语法等

&#x1f38a;专栏【MySQL】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f354;概述&#x1f354;索引结构⭐B-Tree多路平衡查找树&#x1f3f3;️‍&a…

前端技术Vue学习笔记--001

前端技术Vue学习笔记 文章目录 前端技术Vue学习笔记1、Vue2和Vue3比较2、Vue简介3、Vue快速上手4、插值表达式{{}}5、Vue响应式特性6、Vue指令6.1、v-html指令6.2、v-show指令和v-if指令6.3、v-else指令和v-else-if指令6.4、v-on指令6.4.1、v-on指令基础6.4.2、v-on调用传参 6.…

win10电脑便签常驻桌面怎么设置?

你是否曾经因为繁忙的工作而忘记了一些重要的事项&#xff1f;相信很多人都会回答&#xff1a;忘记过&#xff01;其实在快节奏的职场中&#xff0c;我们经常需要记录一些重要的信息&#xff0c;例如会议时间、约见客户时间、今天需要完成的工作任务等。而为了能够方便地记录和…

阿里云安装宝塔面板

阿里云安装宝塔面板 1.安装步骤2.需要加入安全组&#xff0c;打开端口3.安装宝塔 1.安装步骤 1.这里主要以阿里云的服务器 ECS为例子,需要安装纯净的系统 创建过程: 这边先用的是免费的: 2.需要加入安全组&#xff0c;打开端口 进入实例选项卡&#xff1a; 快速添加&…

低代码平台协同OA升级,促进金融企业信息化建设

编者按&#xff1a;数字化办公是信息化时代每个企业不可避免的&#xff0c;OA系统是数字化办公的关键环节。如何与时俱进&#xff0c;保持企业的活力&#xff0c;增强企业综合竞争力&#xff1f;本文分析了企业OA系统为什么需要升级&#xff0c;并进一步指出如何实现升级。 关…

Linux环境下Elasticsearch相关软件安装

Linux环境下Elasticsearch相关软件安装 本文将介绍在linux(Centos7)环境下安装Elasticsearch相关的软件。 1、安装Elasticsearch 1.1 Elasticsearch下载 首先去Elasticsearch官网下载相应版本的安装包&#xff0c;下载之后传输到linux服务器上。 官网地址&#xff1a;http…

在自定义数据集上微调Alpaca和LLaMA

本文将介绍使用LoRa在本地机器上微调Alpaca和LLaMA&#xff0c;我们将介绍在特定数据集上对Alpaca LoRa进行微调的整个过程&#xff0c;本文将涵盖数据处理、模型训练和使用流行的自然语言处理库(如Transformers和hugs Face)进行评估。此外还将介绍如何使用grado应用程序部署和…

STM32MP157驱动开发——LED驱动(设备树)

文章目录 设备树驱动模型如何使用设备树写驱动程序设备树节点要与 platform_driver 能匹配设备树节点指定资源&#xff0c;platform_driver 获得资源 LED 模板驱动程序的改造&#xff1a;设备树驱动模型修改设备树&#xff0c;添加 led 设备节点修改 platform_driver 的源码编译…

设计模式再探——状态模式

目录 一、背景介绍二、思路&方案三、过程1.状态模式简介2.状态模式的类图3.状态模式代码4.状态模式还可以优化的地方5.状态模式的项目实战&#xff0c;优化后 四、总结五、升华 一、背景介绍 最近产品中有这样的业务需求&#xff0c;不同时间(这里不是活动的执行时间&…

前端学习——Vue (Day1)

Vue 快速上手 Vue 是什么 创建 Vue 实例 Vue2官网&#xff1a;https://v2.cn.vuejs.org/ <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge…

基于单片机的语音识别智能垃圾桶垃圾分类的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;液晶显示当前信息和状态&#xff1b;通过语音识别模块对当前垃圾种类进行语音识别&#xff1b; 通过蜂鸣器进行声光报警提醒垃圾桶已满&#xff1b;采用舵机控制垃圾桶打开关闭&#xff1b;超声波检测当前垃圾桶满溢程度&#xff1…

【目标跟踪】2、FairMOT | 平衡多目标跟踪中的目标检测和 Re-ID 任务 | IJCV2021

文章目录 一、背景二、方法2.1 Backbone2.2 检测分支2.3 Re-ID 分支2.4 训练 FairMOT2.5 Online Inference 三、效果3.1 数据集3.2 实现细节3.3 消融实验3.4 最终效果 论文&#xff1a;FairMOT: On the Fairness of Detection and Re-Identification in Multiple Object Tracki…

大模型开发(十):Chat Completion Models API 详解

全文共8000余字&#xff0c;预计阅读时间约18~28分钟 | 满满干货(附代码案例)&#xff0c;建议收藏&#xff01; 本文目标&#xff1a;详解Chat Completion Models的参数及应用实例&#xff0c;并基于该API实现一个本地知识库的多轮对话智能助理 代码&文件下载点这里 一、…

【前端|CSS系列第4篇】CSS布局之网格布局

前言 最近在做的一个项目前台首页有一个展示词条的功能&#xff0c;每一个词条都以一个固定大小的词条卡片进行展示&#xff0c;要将所有的词条卡片展示出来&#xff0c;大概是下面这种布局 每一行的卡片数目会随着屏幕大小自动变化&#xff0c;并且希望整个卡片区域周围不要…

ChatGPT 4.0 —— Code Interpreter

&#x1f4ce;产品销售数据集.csv 选取以上的数据集作为输入&#xff0c;对Code Interpreter 进行测试 1.输入指定数据集&#xff0c;要求给出该数据集的概貌 2.请分析销售的总金额和其他变量的关系 Python Script: # Import required libraries import matplotlib.pyplot a…

java项目之网络视频播放器(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的网络视频播放器。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&a…

vs2015 工程组织与动态加载

10.Visual Studio动态加载_哔哩哔哩_bilibili 1.工程组织 ① researcher.cpp #include "nn/nn.h"#include "nn/factory.h" #include "nn/factory_impl/factory_impl.h"#include <iostream>int main() {int ret 0;factory_i* fct new f…

Java实现获取客户端真实IP方法小结

Java实现获取客户端真实IP方法小结 在jsP里&#xff0c;获取客户端的IP地址的方法是&#xff1a;request.getRemoteAddr()&#xff0c;这种方法在大部分情况下都是有效的。但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了。如果使用了反向代理软件&am…

【数据挖掘】bytewax 与 ydata工具可实时了解您的数据

一、说明 在这篇博文中&#xff0c;我们将介绍如何将开源流式处理解决方案 bytewax 与 ydata 分析相结合并加以利用&#xff0c;以提高流式处理流的质量。 STream 处理支持在传输中和存储之前对数据进行实时分析&#xff0c;并且可以是有状态的&#xff0c;也可以是无状态的。 …