分布式事务框架 seata-golang 通信模型详解

简介: Java 的世界里,大家广泛使用的一个高性能网络通信框架 netty,很多 RPC 框架都是基于 netty 来实现的。在 golang 的世界里,getty 也是一个类似 netty 的高性能网络通信库。getty 最初由 dubbogo 项目负责人于雨开发,作为底层通信库在 dubbo-go 中使用。随着 dubbo-go 捐献给 apache 基金会,在社区小伙伴的共同努力下,getty 也最终进入到 apache 这个大家庭,并改名 dubbo-getty 。

头图.png

作者 | 刘晓敏 于雨

一、简介

Java 的世界里,大家广泛使用的一个高性能网络通信框架 netty,很多 RPC 框架都是基于 netty 来实现的。在 golang 的世界里,getty 也是一个类似 netty 的高性能网络通信库。getty 最初由 dubbogo 项目负责人于雨开发,作为底层通信库在 dubbo-go 中使用。随着 dubbo-go 捐献给 apache 基金会,在社区小伙伴的共同努力下,getty 也最终进入到 apache 这个大家庭,并改名 dubbo-getty 。

18 年的时候,我在公司里实践微服务,当时遇到最大的问题就是分布式事务问题。同年,阿里在社区开源他们的分布式事务解决方案,我也很快关注到这个项目,起初还叫 fescar,后来更名 seata。由于我对开源技术很感兴趣,加了很多社区群,当时也很关注 dubbo-go 这个项目,在里面默默潜水。随着对 seata 的了解,逐渐萌生了做一个 go 版本的分布式事务框架的想法。

要做一个 golang 版的分布式事务框架,首要的一个问题就是如何实现 RPC 通信。dubbo-go 就是很好的一个例子摆在眼前,遂开始研究 dubbo-go 的底层 getty。

二、如何基于 getty 实现 RPC 通信

getty 框架的整体模型图如下:

1.png

下面结合相关代码,详述 seata-golang 的 RPC 通信过程。

1. 建立连接

实现 RPC 通信,首先要建立网络连接吧,我们从 client.go 开始看起。

func (c *client) connect() {var (err errorss  Session)for {// 建立一个 session 连接ss = c.dial()if ss == nil {// client has been closedbreak}err = c.newSession(ss)if err == nil {// 收发报文ss.(*session).run()// 此处省略部分代码break}// don't distinguish between tcp connection and websocket connection. Because// gorilla/websocket/conn.go:(Conn)Close also invoke net.Conn.Close()ss.Conn().Close()}
}

connect() 方法通过 dial() 方法得到了一个 session 连接,进入 dial() 方法:

func (c *client) dial() Session {switch c.endPointType {case TCP_CLIENT:return c.dialTCP()case UDP_CLIENT:return c.dialUDP()case WS_CLIENT:return c.dialWS()case WSS_CLIENT:return c.dialWSS()}return nil
}

我们关注的是 TCP 连接,所以继续进入 c.dialTCP() 方法:

func (c *client) dialTCP() Session {var (err  errorconn net.Conn)for {if c.IsClosed() {return nil}if c.sslEnabled {if sslConfig, err := c.tlsConfigBuilder.BuildTlsConfig(); err == nil && sslConfig != nil {d := &net.Dialer{Timeout: connectTimeout}// 建立加密连接conn, err = tls.DialWithDialer(d, "tcp", c.addr, sslConfig)}} else {// 建立 tcp 连接conn, err = net.DialTimeout("tcp", c.addr, connectTimeout)}if err == nil && gxnet.IsSameAddr(conn.RemoteAddr(), conn.LocalAddr()) {conn.Close()err = errSelfConnect}if err == nil {// 返回一个 TCPSessionreturn newTCPSession(conn, c)}log.Infof("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, connectTimeout, perrors.WithStack(err))<-wheel.After(connectInterval)}
}

至此,我们知道了 getty 如何建立 TCP 连接,并返回 TCPSession。

2. 收发报文

那它是怎么收发报文的呢,我们回到 connection 方法接着往下看,有这样一行 ss.(*session).run(),在这行代码之后代码都是很简单的操作,我们猜测这行代码运行的逻辑里面一定包含收发报文的逻辑,接着进入 run() 方法:

func (s *session) run() {// 省略部分代码go s.handleLoop()go s.handlePackage()
}


这里起了两个 goroutine,handleLoop 和 handlePackage,看字面意思符合我们的猜想,进入 handleLoop() 方法:

func (s *session) handleLoop() {// 省略部分代码for {// A select blocks until one of its cases is ready to run.// It choose one at random if multiple are ready. Otherwise it choose default branch if none is ready.select {// 省略部分代码case outPkg, ok = <-s.wQ:// 省略部分代码iovec = iovec[:0]for idx := 0; idx < maxIovecNum; idx++ {// 通过 s.writer 将 interface{} 类型的 outPkg 编码成二进制的比特pkgBytes, err = s.writer.Write(s, outPkg)// 省略部分代码iovec = append(iovec, pkgBytes)//省略部分代码}// 将这些二进制比特发送出去err = s.WriteBytesArray(iovec[:]...)if err != nil {log.Errorf("%s, [session.handleLoop]s.WriteBytesArray(iovec len:%d) = error:%+v",s.sessionToken(), len(iovec), perrors.WithStack(err))s.stop()// break LOOPflag = false}case <-wheel.After(s.period):if flag {if wsFlag {err := wsConn.writePing()if err != nil {log.Warnf("wsConn.writePing() = error:%+v", perrors.WithStack(err))}}// 定时执行的逻辑,心跳等s.listener.OnCron(s)}}}
}

通过上面的代码,我们不难发现,handleLoop() 方法处理的是发送报文的逻辑,RPC 需要发送的消息首先由 s.writer 编码成二进制比特,然后通过建立的 TCP 连接发送出去。这个 s.writer 对应的 Writer 接口是 RPC 框架必须要实现的一个接口。

继续看 handlePackage() 方法:

func (s *session) handlePackage() {// 省略部分代码if _, ok := s.Connection.(*gettyTCPConn); ok {if s.reader == nil {errStr := fmt.Sprintf("session{name:%s, conn:%#v, reader:%#v}", s.name, s.Connection, s.reader)log.Error(errStr)panic(errStr)}err = s.handleTCPPackage()} else if _, ok := s.Connection.(*gettyWSConn); ok {err = s.handleWSPackage()} else if _, ok := s.Connection.(*gettyUDPConn); ok {err = s.handleUDPPackage()} else {panic(fmt.Sprintf("unknown type session{%#v}", s))}
}

进入 handleTCPPackage() 方法:

func (s *session) handleTCPPackage() error {// 省略部分代码conn = s.Connection.(*gettyTCPConn)for {// 省略部分代码bufLen = 0for {// for clause for the network timeout condition check// s.conn.SetReadTimeout(time.Now().Add(s.rTimeout))// 从 TCP 连接中收到报文bufLen, err = conn.recv(buf)// 省略部分代码break}// 省略部分代码// 将收到的报文二进制比特写入 pkgBufpktBuf.Write(buf[:bufLen])for {if pktBuf.Len() <= 0 {break}// 通过 s.reader 将收到的报文解码成 RPC 消息pkg, pkgLen, err = s.reader.Read(s, pktBuf.Bytes())// 省略部分代码s.UpdateActive()// 将收到的消息放入 TaskQueue 供 RPC 消费端消费s.addTask(pkg)pktBuf.Next(pkgLen)// continue to handle case 5}if exit {break}}return perrors.WithStack(err)
}

从上面的代码逻辑我们分析出,RPC 消费端需要将从 TCP 连接收到的二进制比特报文解码成 RPC 能消费的消息,这个工作由 s.reader 实现,所以,我们要构建 RPC 通信层也需要实现 s.reader 对应的 Reader 接口。

3. 底层处理网络报文的逻辑如何与业务逻辑解耦

我们都知道,netty 通过 boss 线程和 worker 线程实现了底层网络逻辑和业务逻辑的解耦。那么,getty 是如何实现的呢?

在 handlePackage() 方法最后,我们看到,收到的消息被放入了 s.addTask(pkg) 这个方法,接着往下分析:

func (s *session) addTask(pkg interface{}) {f := func() {s.listener.OnMessage(s, pkg)s.incReadPkgNum()}if taskPool := s.EndPoint().GetTaskPool(); taskPool != nil {taskPool.AddTaskAlways(f)return}f()
}

pkg 参数传递到了一个匿名方法,这个方法最终放入了 taskPool。这个方法很关键,在我后来写 seata-golang 代码的时候,就遇到了一个坑,这个坑后面分析。

接着我们看一下 taskPool 的定义:

// NewTaskPoolSimple build a simple task pool
func NewTaskPoolSimple(size int) GenericTaskPool {if size < 1 {size = runtime.NumCPU() * 100}return &taskPoolSimple{work: make(chan task),sem:  make(chan struct{}, size),done: make(chan struct{}),}
}

构建了一个缓冲大小为 size (默认为  runtime.NumCPU() * 100) 的 channel sem。再看方法 AddTaskAlways(t task)

func (p *taskPoolSimple) AddTaskAlways(t task) {select {case <-p.done:returndefault:}select {case p.work <- t:returndefault:}select {case p.work <- t:case p.sem <- struct{}{}:p.wg.Add(1)go p.worker(t)default:goSafely(t)}
}

加入的任务,会先由 len(p.sem) 个 goroutine 去消费,如果没有 goroutine 空闲,则会启动一个临时的 goroutine 去运行 t()。相当于有  len(p.sem) 个 goroutine 组成了 goroutine pool,pool 中的 goroutine 去处理业务逻辑,而不是由处理网络报文的 goroutine 去运行业务逻辑,从而实现了解耦。写 seata-golang 时遇到的一个坑,就是忘记设置 taskPool 造成了处理业务逻辑和处理底层网络报文逻辑的 goroutine 是同一个,我在业务逻辑中阻塞等待一个任务完成时,阻塞了整个 goroutine,使得阻塞期间收不到任何报文。

4. 具体实现

下面的代码见 getty.go:

// Reader is used to unmarshal a complete pkg from buffer
type Reader interface {Read(Session, []byte) (interface{}, int, error)
}// Writer is used to marshal pkg and write to session
type Writer interface {// if @Session is udpGettySession, the second parameter is UDPContext.Write(Session, interface{}) ([]byte, error)
}// ReadWriter interface use for handle application packages
type ReadWriter interface {ReaderWriter
}
// EventListener is used to process pkg that received from remote session
type EventListener interface {// invoked when session opened// If the return error is not nil, @Session will be closed.OnOpen(Session) error// invoked when session closed.OnClose(Session)// invoked when got error.OnError(Session, error)// invoked periodically, its period can be set by (Session)SetCronPeriodOnCron(Session)// invoked when getty received a package. Pls attention that do not handle long time// logic processing in this func. You'd better set the package's maximum length.// If the message's length is greater than it, u should should return err in// Reader{Read} and getty will close this connection soon.//// If ur logic processing in this func will take a long time, u should start a goroutine// pool(like working thread pool in cpp) to handle the processing asynchronously. Or u// can do the logic processing in other asynchronous way.// !!!In short, ur OnMessage callback func should return asap.//// If this is a udp event listener, the second parameter type is UDPContext.OnMessage(Session, interface{})
}

通过对整个 getty 代码的分析,我们只要实现  ReadWriter 来对 RPC  消息编解码,再实现 EventListener 来处理 RPC 消息的对应的具体逻辑,将 ReadWriter 实现和 EventLister 实现注入到 RPC 的 Client 和 Server 端,则可实现 RPC 通信。

4.1 编解码协议实现

下面是 seata 协议的定义:

2.png

在 ReadWriter 接口的实现 RpcPackageHandler 中,调用 Codec 方法对消息体按照上面的格式编解码:

// 消息编码为二进制比特
func MessageEncoder(codecType byte, in interface{}) []byte {switch codecType {case SEATA:return SeataEncoder(in)default:log.Errorf("not support codecType, %s", codecType)return nil}
}// 二进制比特解码为消息体
func MessageDecoder(codecType byte, in []byte) (interface{}, int) {switch codecType {case SEATA:return SeataDecoder(in)default:log.Errorf("not support codecType, %s", codecType)return nil, 0}
}

4.2 Client 端实现

再来看 client 端 EventListener 的实现 RpcRemotingClient

func (client *RpcRemoteClient) OnOpen(session getty.Session) error {go func() request := protocal.RegisterTMRequest{AbstractIdentifyRequest: protocal.AbstractIdentifyRequest{ApplicationId:           client.conf.ApplicationId,TransactionServiceGroup: client.conf.TransactionServiceGroup,}}// 建立连接后向 Transaction Coordinator 发起注册 TransactionManager 的请求_, err := client.sendAsyncRequestWithResponse(session, request, RPC_REQUEST_TIMEOUT)if err == nil {// 将与 Transaction Coordinator 建立的连接保存在连接池供后续使用clientSessionManager.RegisterGettySession(session)client.GettySessionOnOpenChannel <- session.RemoteAddr()}}()return nil
}// OnError ...
func (client *RpcRemoteClient) OnError(session getty.Session, err error) {clientSessionManager.ReleaseGettySession(session)
}// OnClose ...
func (client *RpcRemoteClient) OnClose(session getty.Session) {clientSessionManager.ReleaseGettySession(session)
}// OnMessage ...
func (client *RpcRemoteClient) OnMessage(session getty.Session, pkg interface{}) {log.Info("received message:{%v}", pkg)rpcMessage, ok := pkg.(protocal.RpcMessage)if ok {heartBeat, isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage)if isHeartBeat && heartBeat == protocal.HeartBeatMessagePong {log.Debugf("received PONG from %s", session.RemoteAddr())}}if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST ||rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY {log.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body)// 处理事务消息,提交 or 回滚client.onMessage(rpcMessage, session.RemoteAddr())} else {resp, loaded := client.futures.Load(rpcMessage.Id)if loaded {response := resp.(*getty2.MessageFuture)response.Response = rpcMessage.Bodyresponse.Done <- trueclient.futures.Delete(rpcMessage.Id)}}
}// OnCron ...
func (client *RpcRemoteClient) OnCron(session getty.Session) {// 发送心跳client.defaultSendRequest(session, protocal.HeartBeatMessagePing)
}

clientSessionManager.RegisterGettySession(session) 的逻辑将在下文中分析。

4.3 Server 端 Transaction Coordinator 实现

代码见 DefaultCoordinator

func (coordinator *DefaultCoordinator) OnOpen(session getty.Session) error {log.Infof("got getty_session:%s", session.Stat())return nil
}func (coordinator *DefaultCoordinator) OnError(session getty.Session, err error) {// 释放 TCP 连接SessionManager.ReleaseGettySession(session)session.Close()log.Errorf("getty_session{%s} got error{%v}, will be closed.", session.Stat(), err)
}func (coordinator *DefaultCoordinator) OnClose(session getty.Session) {log.Info("getty_session{%s} is closing......", session.Stat())
}func (coordinator *DefaultCoordinator) OnMessage(session getty.Session, pkg interface{}) {log.Debugf("received message:{%v}", pkg)rpcMessage, ok := pkg.(protocal.RpcMessage)if ok {_, isRegTM := rpcMessage.Body.(protocal.RegisterTMRequest)if isRegTM {// 将 TransactionManager 信息和 TCP 连接建立映射关系coordinator.OnRegTmMessage(rpcMessage, session)return}heartBeat, isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage)if isHeartBeat && heartBeat == protocal.HeartBeatMessagePing {coordinator.OnCheckMessage(rpcMessage, session)return}if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST ||rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY {log.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body)_, isRegRM := rpcMessage.Body.(protocal.RegisterRMRequest)if isRegRM {// 将 ResourceManager 信息和 TCP 连接建立映射关系coordinator.OnRegRmMessage(rpcMessage, session)} else {if SessionManager.IsRegistered(session) {defer func() {if err := recover(); err != nil {log.Errorf("Catch Exception while do RPC, request: %v,err: %w", rpcMessage, err)}}()// 处理事务消息,全局事务注册、分支事务注册、分支事务提交、全局事务回滚等coordinator.OnTrxMessage(rpcMessage, session)} else {session.Close()log.Infof("close a unhandled connection! [%v]", session)}}} else {resp, loaded := coordinator.futures.Load(rpcMessage.Id)if loaded {response := resp.(*getty2.MessageFuture)response.Response = rpcMessage.Bodyresponse.Done <- truecoordinator.futures.Delete(rpcMessage.Id)}}}
}func (coordinator *DefaultCoordinator) OnCron(session getty.Session) {}

coordinator.OnRegTmMessage(rpcMessage, session) 注册 Transaction Manager,coordinator.OnRegRmMessage(rpcMessage, session) 注册 Resource Manager。具体逻辑分析见下文。

消息进入 coordinator.OnTrxMessage(rpcMessage, session) 方法,将按照消息的类型码路由到具体的逻辑当中:

    switch msg.GetTypeCode() {case protocal.TypeGlobalBegin:req := msg.(protocal.GlobalBeginRequest)resp := coordinator.doGlobalBegin(req, ctx)return respcase protocal.TypeGlobalStatus:req := msg.(protocal.GlobalStatusRequest)resp := coordinator.doGlobalStatus(req, ctx)return respcase protocal.TypeGlobalReport:req := msg.(protocal.GlobalReportRequest)resp := coordinator.doGlobalReport(req, ctx)return respcase protocal.TypeGlobalCommit:req := msg.(protocal.GlobalCommitRequest)resp := coordinator.doGlobalCommit(req, ctx)return respcase protocal.TypeGlobalRollback:req := msg.(protocal.GlobalRollbackRequest)resp := coordinator.doGlobalRollback(req, ctx)return respcase protocal.TypeBranchRegister:req := msg.(protocal.BranchRegisterRequest)resp := coordinator.doBranchRegister(req, ctx)return respcase protocal.TypeBranchStatusReport:req := msg.(protocal.BranchReportRequest)resp := coordinator.doBranchReport(req, ctx)return respdefault:return nil}

4.4 session manager 分析

Client 端同 Transaction Coordinator 建立连接起连接后,通过 clientSessionManager.RegisterGettySession(session) 将连接保存在 serverSessions = sync.Map{} 这个 map 中。map 的 key 为从 session 中获取的 RemoteAddress 即 Transaction Coordinator 的地址,value 为 session。这样,Client 端就可以通过 map 中的一个 session 来向 Transaction Coordinator 注册 Transaction Manager 和 Resource Manager 了。具体代码见 getty_client_session_manager.go

Transaction Manager 和 Resource Manager 注册到 Transaction Coordinator 后,一个连接既有可能用来发送 TM 消息也有可能用来发送 RM 消息。我们通过 RpcContext 来标识一个连接信息:

type RpcContext struct {Version                 stringTransactionServiceGroup stringClientRole              meta.TransactionRoleApplicationId           stringClientId                stringResourceSets            *model.SetSession                 getty.Session
}

当收到事务消息时,我们需要构造这样一个 RpcContext 供后续事务处理逻辑使用。所以,我们会构造下列 map 来缓存映射关系:

var (// session -> transactionRole// TM will register before RM, if a session is not the TM registered,// it will be the RM registeredsession_transactionroles = sync.Map{}// session -> applicationIdidentified_sessions = sync.Map{}// applicationId -> ip -> port -> sessionclient_sessions = sync.Map{}// applicationId -> resourceIdsclient_resources = sync.Map{}
)

这样,Transaction Manager 和 Resource Manager 分别通过 coordinator.OnRegTmMessage(rpcMessage, session)和 coordinator.OnRegRmMessage(rpcMessage, session) 注册到 Transaction Coordinator 时,会在上述 client_sessions map 中缓存 applicationId、ip、port 与 session 的关系,在 client_resources map 中缓存 applicationId 与 resourceIds(一个应用可能存在多个 Resource Manager) 的关系。在需要时,我们就可以通过上述映射关系构造一个 RpcContext。这部分的实现和 java 版 seata 有很大的不同,感兴趣的可以深入了解一下。具体代码见 getty_session_manager.go

至此,我们就分析完了 seata-golang 整个 RPC 通信模型的机制。

三、seata-golang 的未来

seata-golang  从今年 4 月份开始开发,到 8 月份基本实现和 java 版 seata 1.2 协议的互通,对 mysql 数据库实现了 AT 模式(自动协调分布式事务的提交回滚),实现了 TCC 模式,TC 端使用 mysql 存储数据,使 TC 变成一个无状态应用支持高可用部署。下图展示了 AT 模式的原理:

3.png

后续,还有许多工作可以做,比如:对注册中心的支持、对配置中心的支持、和 java 版 seata 1.4 的协议互通、其他数据库的支持、raft transaction coordinator 的实现等,希望对分布式事务问题感兴趣的开发者可以加入进来一起来打造一个完善的 golang 的分布式事务框架。如果你有任何疑问,欢迎加入交流群【钉钉群号 33069364】。

另外,欢迎对 dubbogo 感兴趣的朋友到 dubbogo 社区钉钉群(钉钉群号 31363295)沟通 dubbogo 技术问题。

 

原文链接
本文为阿里云原创内容,未经允许不得转载。

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

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

相关文章

移动云TeaTalk(长沙站)| 聚焦“云网一体”发展新机遇

聚焦“云网一体”发展新机遇移动云TeaTalk&#xff08;长沙站&#xff09;云网一体&#xff0c;“湘”遇“湘”融 。7月23日&#xff0c; 移动云TeaTalk沙龙第四站在长沙成功举办。活动围绕中国移动“云网一体”理念&#xff0c;介绍云网产品体系规划以及各领域产品应用&#x…

宜泊科技加入阿里云原生合作伙伴计划,共建智慧停车新生态

简介&#xff1a; 在过去几年里&#xff0c;“云原生”的概念逐渐被广大的企业所熟知。云原生给企业带来了巨大的价值&#xff1a;一方面&#xff0c;统一的容器编排角度&#xff0c;给企业带来了更好的资源利用率和管理的效率&#xff1b;另一方面&#xff0c;云原生的技术实践…

红旗Linux网卡绑定,Linux bonding 之balance-alb 原理介绍及其实现

要求&#xff1a;首先要安装bonding driver 和 ifenslave。bonding driver 是实现网卡绑定的软件基础&#xff0c;它在很多系统中都作为模块而加载。ifenslave 是实现网卡绑定的一个实用工具。检查系统是否符合这两个要求&#xff1a;[rootnode1 bin]# lsmod | grep bondbondin…

大数据时代,如何用数据驱动精准营销

简介&#xff1a; 针对Yeahmobi在全球业务中需要统一治理数据资产和提供高并发、高弹性的存储服务需求&#xff0c;阿里云为Yeahmobi构建了一站式的数据湖解决方案。 Yeahmobi介绍 Yeahmobi是一家技术驱动发展的国际化智能营销服务公司&#xff0c;致力于为客户提供精准的全球…

百度获得国内首批无人驾驶乘用车高速公路道路测试资质

7月27日&#xff0c;第二十三届中国科协年会在北京拉开帷幕。在自动驾驶汽车产业落地与示范运营分论坛上&#xff0c;北京市高级别自动驾驶示范区推进工作组负责人孔磊介绍了《北京市高级别自动驾驶示范区建设阶段成果及2.0规划》&#xff0c;并向部分企业颁发了国内首批乘用车…

排查指南 | 关于 mPaaS-iOS 小程序打不开问题的解决方案

简介&#xff1a; 让天下没有打不开的小程序&#xff01;&#xff01;&#xff01; 在我们集成 mPaaS 插件并使用小程序的过程中&#xff0c;很多开发者遇到了打不开小程序的问题。今天就举例说明&#xff0c;开发者在完成基本接入后&#xff0c;尝试打开 H5 应用&#xff0c;但…

十万亿级OLAP引擎解读-AnalyticDB如何支撑数据银行超大规模低成本实时分析

简介&#xff1a; 数据银行是一款品牌消费者运营的商业数据产品&#xff0c;由于其核心分析能力需要在海量数据上实现任意维度自由分析和响应时间上的强需求&#xff0c;我们大规模使用AnalyticDB作为底层的分析引擎&#xff0c;最终以较低的成本&#xff0c;出色的性能&#x…

C-Power 自主无人海上电力系统有望通过高效能源传输及远程高带宽通信技术,开启新的海上应用

开发利用海浪能是一个快速发展的领域&#xff0c;海洋工程师正在改进技术&#xff0c;以提供可靠、经济高效的海洋能源发与存储&#xff0c;并实现全新的海上数据与通信服务。 哥伦比亚电力技术公司 (C-Power) 位于俄勒冈州科瓦利斯市&#xff0c;是波浪能系统的全球领先企业&…

OCP China Day 2021:开放计算十年,以全球协作打破创新边界

7月27日&#xff0c;由 OCP 社区主办、浪潮承办的第三届 OCP China Day 2021在北京举行&#xff0c;近千名IT工程师和数据中心从业者参会。开放计算正成为当前乃至未来数据中心的创新主力&#xff0c;通过全球化协作的创新模式&#xff0c;解决数据中心基础设施可持续发展的重大…

通信行业力推零信任标准,蔷薇灵动微隔离首批获证

7月27日至28日&#xff0c;以“数字裂变&#xff0c;可信发展”为主题的2021可信云大会在京拉开帷幕。大会邀请云计算行业专家学者、知名企业代表、行业大咖同台论道&#xff0c;围绕行业发展趋势、落地应用、新兴技术、企业数字化上云等热门议题展开观点碰撞&#xff0c;带来前…

万张图片,流畅体验——记一次 Vue 列表渲染

简介&#xff1a; ### 背景 团队目前的Web端产品中需要显示两个列表视图&#xff1a;卡片列表和条目列表&#xff0c;并且在点击切换按钮的时候&#xff0c;对两个列表进行切换显示。 ![条目列表](https://ata2-img.oss-cn-zh 背景 团队目前的Web端产品中需要显示两个列表视图…

我的世界服务器linux加mod,在Linux下搭建带MOD 我的世界(Minecraft)服务器

在Linux下搭建带MOD 我的世界(Minecraft)服务器系统要求官方服务器推荐配置要求如下&#xff1a;CPU&#xff1a;Intel Core-Based CPUs or AMD K8-Based CPUs IBM 970 2.0 GHz and better内存&#xff1a;5 GiB硬盘空间&#xff1a;16 GiB上行宽带&#xff1a;8 Mbit/s下行宽带…

分布式、云原生技术之后,分布式云或成数字化转型新利器

编辑 | 宋 慧 出品 | CSDN云计算 头图 | 2021可信云大会现场 7月27日&#xff0c;2021年可信云大会在京顺利开幕。本届大会以“数字裂变&#xff0c;可信发展”为主题&#xff0c;云计算行业专家学者、众多国内一线云计算厂商、头部客户同台论道&#xff0c;围绕云计算行业趋势…

2021 ISC会上山石网科重磅发布智能下一代防火墙A系列,重新定义边界安全防御

勒索病毒频繁、威胁隐匿于加密流量、高级威胁藏于内部、物联网安全盲区众多&#xff0c;数字化发展及其带来的网络威胁态势正在发生着质的变化&#xff0c;网络安全所要求的防护能力不断提高。防火墙作为企业安全基础架构最重要的基石之一&#xff0c;其辐射到企业的边界、内网…

linux可平通网关但不能上网,redhat问题:能ping通网关和本网段的IP,但是不能ping通DNS,也不能上网...

redhat问题&#xff1a;能ping通网关和本网段的IP&#xff0c;但是不能ping通DNS&#xff0c;也不能上网(2011-12-20 06:11:51)标签&#xff1a;上网杂谈redhat问题&#xff1a;能ping通网关和本网段的IP&#xff0c;但是不能ping通DNS&#xff0c;也不能上网查看路由的信息如下…

一文读懂DataOps

作者&#xff1a;彭锋 宋文欣等来源&#xff1a;智领云科技大部分企业的数据平台建设要想顺利过渡到第三阶段&#xff0c;则离不开一个关键方法论—DataOps&#xff08;数据运维&#xff09;的帮助。DataOps 与 DevOps 十分形似&#xff0c;也有着与 DevOps 类似的软件开发角色…

Linux系统初学者指南,观点|Linux 系统调用的初学者指南

在过去的几年中&#xff0c;我一直在做大量容器相关的工作。先前&#xff0c;我看到 Julien Friedman 的一个很棒的演讲&#xff0c;它用几行 Go 语言写了一个容器框架。这让我突然了解到容器只是一个受限的 Linux 进程中的机器。构建这个受限视图涉及到 Golang 系统调用包中的…

英特尔携手百度全方位深化合作 共筑智能生态

2021年7月29日&#xff0c;英特尔公司今日出席智能经济高峰论坛暨百度云智峰会2021并分享了一系列与百度在人工智能、云计算、智能边缘等方面的最新合作进展。在智能技术方面&#xff0c;百度基于第三代英特尔至强可扩展处理器&#xff0c;打造全功能AI开发平台Baidu Machine L…

山石网科蒋东毅:网络连接矩阵复杂化,传统安全防护框架需重构

编辑 | 宋慧 供稿 | 山石网科 头图 | 蒋东毅在 ISC 2021主题论坛发表演讲 7月28日上午&#xff0c;在ISC 2021 第九届互联网安全大会主题论坛上&#xff0c;山石网科高级副总裁、首席战略官&#xff08;CSO&#xff09;蒋东毅带来了一场主题为《政企安全面临的多重挑战和未来趋…

如何使用java来实现windows系统关机

可以使用Java代码来调用操作系统的命令行来实现Windows关机操作。具体步骤如下&#xff1a; import java.io.IOException;public class ShutdownWindows {public static void main(String[] args) {try {// 调用命令行执行关机命令Process process Runtime.getRuntime().exec…