热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于gin+websocket单台机器支持百万连接分布式聊天(IM)系统

本文将介绍如何实现一个基于websocket分布式聊天(IM)系统。使用golang实现websocket通讯,单机可以支持百万连接,使用gin框架、

本文将介绍如何实现一个基于websocket分布式聊天(IM)系统。

使用golang实现websocket通讯,单机可以支持百万连接,使用gin框架、nginx负载、可以水平部署、程序内部相互通讯、使用grpc通讯协议。

本文内容比较长,如果直接想clone项目体验直接进入项目体验 goWebSocket项目下载 ,文本从介绍webSocket是什么开始,然后开始介绍这个项目,以及在Nginx中配置域名做webSocket的转发,然后介绍如何搭建一个分布式系统。
 

1、项目说明

1.1 一般项目中webSocket使用的架构图


1.2 项目体验

项目地址 gowebsocket
IM-聊天首页 或者在新的窗口打开 http://im.91vh.com/home/index
打开连接以后进入聊天界面
多人群聊可以同时打开两个窗口

2、介绍webSocket

2.1 webSocket 是什么

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

HTTP和WebSocket在通讯过程的比较

HTTP和webSocket都支持配置证书,ws:// 无证书 wss:// 配置证书的协议标识

 

2.2 webSocket的兼容性

浏览器的兼容性,开始支持webSocket的版本


服务端的支持
golang、java、php、node.js、python、nginx 都有不错的支持

Android和IOS的支持
Android可以使用java-webSocket对webSocket支持

iOS 4.2及更高版本具有WebSockets支持

2.3 为什么要用webSocket

从业务上出发,需要一个主动通达客户端的能力
目前大多数的请求都是使用HTTP,都是由客户端发起一个请求,有服务端处理,然后返回结果,不可以服务端主动向某一个客户端主动发送数据

大多数场景我们需要主动通知用户,如:聊天系统、用户完成任务主动告诉用户、一些运营活动需要通知到在线的用户
可以获取用户在线状态
在没有长连接的时候通过客户端主动轮询获取数据
可以通过一种方式实现,多种不同平台(H5/Android/IOS)去使用


2.4 webSocket建立过程


2.4.1 客户端先发起升级协议的请求

客户端发起升级协议的请求,采用标准的HTTP报文格式,在报文中添加头部信息

Connection: Upgrade 表明连接需要升级

Upgrade: websocket 需要升级到 websocket协议

Sec-WebSocket-Version: 13  协议的版本为13

Sec-WebSocket-Key: I6qjdEaqYljv3+9x+GrhqA== 这个是base64 encode 的值,是浏览器随机生成的,与服务器响应的 Sec-WebSocket-Accept对应

# Request Headers
Connection: Upgrade
Host: im.91vh.com
Origin: http://im.91vh.com
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: I6qjdEaqYljv3+9x+GrhqA==
Sec-WebSocket-Version: 13
Upgrade: websocket


 

2.4.2 服务器响应升级协议

服务端接收到升级协议的请求,如果服务端支持升级协议会做如下响应

返回:

Status Code: 101 Switching Protocols 表示支持切换协议

# Response Headers
Connection: upgrade
Date: Fri, 09 Aug 2019 07:36:59 GMT
Sec-WebSocket-Accept: mB5emvxi2jwTUhDdlRtADuBax9E=
Server: nginx/1.12.1
Upgrade: websocket

2.4.3 升级协议完成以后,客户端和服务器就可以相互发送数据



3、如何实现基于webSocket的长连接系统

3.1 使用go实现webSocket服务端


3.1.1 启动端口监听

websocket需要监听端口,所以需要在golang 成功的 main 函数中用协程的方式去启动程序
main.go 实现启动

go websocket.StartWebSocket()

init_acc.go 启动程序

// 启动程序
func StartWebSocket() {http.HandleFunc("/acc", wsPage)http.ListenAndServe(":8089", nil)
}

3.1.2 升级协议


  • 客户端是通过http请求发送到服务端,我们需要对http协议进行升级为websocket协议
  • 对http请求协议进行升级 golang 库gorilla/websocket 已经做得很好了,我们直接使用就可以了
  • 在实际使用的时候,建议每个连接使用两个协程处理客户端请求数据和向客户端发送数据,虽然开启协程会占用一些内存,但是读取分离,减少收发数据堵塞的可能
  • init_acc.go

func wsPage(w http.ResponseWriter, req *http.Request) {// 升级协议conn, err :&#61; (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {fmt.Println("升级协议", "ua:", r.Header["User-Agent"], "referer:", r.Header["Referer"])return true}}).Upgrade(w, req, nil)if err !&#61; nil {http.NotFound(w, req)return}fmt.Println("webSocket 建立连接:", conn.RemoteAddr().String())currentTime :&#61; uint64(time.Now().Unix())client :&#61; NewClient(conn.RemoteAddr().String(), conn, currentTime)go client.read()go client.write()// 用户连接事件clientManager.Register <- client
}

3.1.3 客户端连接的管理


  • 当前程序有多少用户连接&#xff0c;还需要对用户广播的需要&#xff0c;这里我们就需要一个管理者(clientManager)&#xff0c;处理这些事件:
  • 记录全部的连接、登录用户的可以通过 appId&#43;uuid 查到用户连接
  • 使用map存储&#xff0c;就涉及到多协程并发读写的问题&#xff0c;所以需要加读写锁
  • 定义四个channel &#xff0c;分别处理客户端建立连接、用户登录、断开连接、全员广播事件

// 连接管理
type ClientManager struct {Clients     map[*Client]bool   // 全部的连接ClientsLock sync.RWMutex       // 读写锁Users       map[string]*Client // 登录的用户 // appId&#43;uuidUserLock    sync.RWMutex       // 读写锁Register    chan *Client       // 连接连接处理Login       chan *login        // 用户登录处理Unregister  chan *Client       // 断开连接处理程序Broadcast   chan []byte        // 广播 向全部成员发送数据
}// 初始化
func NewClientManager() (clientManager *ClientManager) {clientManager &#61; &ClientManager{Clients:    make(map[*Client]bool),Users:      make(map[string]*Client),Register:   make(chan *Client, 1000),Login:      make(chan *login, 1000),Unregister: make(chan *Client, 1000),Broadcast:  make(chan []byte, 1000),}return
}

3.1.4 注册客户端的socket的写的异步处理程序


  • 防止发生程序崩溃&#xff0c;所以需要捕获异常
  • 为了显示异常崩溃位置这里使用string(debug.Stack())打印调用堆栈信息
  • 如果写入数据失败了&#xff0c;可能连接有问题&#xff0c;就关闭连接
  • client.go

// 向客户端写数据
func (c *Client) write() {defer func() {if r :&#61; recover(); r !&#61; nil {fmt.Println("write stop", string(debug.Stack()), r)}}()defer func() {clientManager.Unregister <- cc.Socket.Close()fmt.Println("Client发送数据 defer", c)}()for {select {case message, ok :&#61; <-c.Send:if !ok {// 发送数据错误 关闭连接fmt.Println("Client发送数据 关闭连接", c.Addr, "ok", ok)return}c.Socket.WriteMessage(websocket.TextMessage, message)}}
}

3.1.5 注册客户端的socket的读的异步处理程序


  • 循环读取客户端发送的数据并处理
  • 如果读取数据失败了&#xff0c;关闭channel
  • client.go

// 读取客户端数据
func (c *Client) read() {defer func() {if r :&#61; recover(); r !&#61; nil {fmt.Println("write stop", string(debug.Stack()), r)}}()defer func() {fmt.Println("读取客户端数据 关闭send", c)close(c.Send)}()for {_, message, err :&#61; c.Socket.ReadMessage()if err !&#61; nil {fmt.Println("读取客户端数据 错误", c.Addr, err)return}// 处理程序fmt.Println("读取客户端数据 处理:", string(message))ProcessData(c, message)}
}

3.1.6 接收客户端数据并处理


  • 约定发送和接收请求数据格式&#xff0c;为了js处理方便&#xff0c;采用了json的数据格式发送和接收数据(人类可以阅读的格式在工作开发中使用是比较方便的)
  • 登录发送数据示例:

{"seq":"1565336219141-266129","cmd":"login","data":{"userId":"马远","appId":101}}

  • 登录响应数据示例:

{"seq":"1565336219141-266129","cmd":"login","response":"code":200,"codeMsg":"Success","data":null}}

  • websocket是双向的数据通讯&#xff0c;可以连续发送&#xff0c;如果发送的数据需要服务端回复&#xff0c;就需要一个seq来确定服务端的响应是回复哪一次的请求数据
  • cmd 是用来确定动作&#xff0c;websocket没有类似于http的url,所以规定 cmd 是什么动作
  • 目前的动作有:login/heartbeat 用来发送登录请求和连接保活(长时间没有数据发送的长连接容易被浏览器、移动中间商、nginx、服务端程序断开)
  • 为什么需要AppId,UserId是表示用户的唯一字段&#xff0c;设计的时候为了做成通用性&#xff0c;设计AppId用来表示用户在哪个平台登录的(web、app、ios等)&#xff0c;方便后续扩展
  • request_model.go 约定的请求数据格式

/************************  请求数据  **************************/
// 通用请求数据格式
type Request struct {Seq  string      &#96;json:"seq"&#96;            // 消息的唯一IdCmd  string      &#96;json:"cmd"&#96;            // 请求命令字Data interface{} &#96;json:"data,omitempty"&#96; // 数据 json
}// 登录请求数据
type Login struct {ServiceToken string &#96;json:"serviceToken"&#96; // 验证用户是否登录AppId        uint32 &#96;json:"appId,omitempty"&#96;UserId       string &#96;json:"userId,omitempty"&#96;
}// 心跳请求数据
type HeartBeat struct {UserId string &#96;json:"userId,omitempty"&#96;
}

  • response_model.go

/************************  响应数据  **************************/
type Head struct {Seq      string    &#96;json:"seq"&#96;      // 消息的IdCmd      string    &#96;json:"cmd"&#96;      // 消息的cmd 动作Response *Response &#96;json:"response"&#96; // 消息体
}type Response struct {Code    uint32      &#96;json:"code"&#96;CodeMsg string      &#96;json:"codeMsg"&#96;Data    interface{} &#96;json:"data"&#96; // 数据 json
}

3.1.7 使用路由的方式处理客户端的请求数据


  • 使用路由的方式处理由客户端发送过来的请求数据
  • 以后添加请求类型以后就可以用类是用http相类似的方式(router-controller)去处理
  • acc_routers.go

// Websocket 路由
func WebsocketInit() {websocket.Register("login", websocket.LoginController)websocket.Register("heartbeat", websocket.HeartbeatController)
}

3.1.8 防止内存溢出和Goroutine不回收

定时任务清除超时连接
没有登录的连接和登录的连接6分钟没有心跳则断开连接
client_manager.go

// 定时清理超时连接
func ClearTimeoutConnections() {currentTime :&#61; uint64(time.Now().Unix())for client :&#61; range clientManager.Clients {if client.IsHeartbeatTimeout(currentTime) {fmt.Println("心跳时间超时 关闭连接", client.Addr, client.UserId, client.LoginTime, client.HeartbeatTime)client.Socket.Close()}}
}

读写的Goroutine有一个失败&#xff0c;则相互关闭
write()Goroutine写入数据失败&#xff0c;关闭c.Socket.Close()连接&#xff0c;会关闭read()Goroutine
read()Goroutine读取数据失败&#xff0c;关闭close(c.Send)连接&#xff0c;会关闭write()Goroutine


客户端主动关闭
关闭读写的Goroutine
从ClientManager删除连接


监控用户连接、Goroutine数
十个内存溢出有九个和Goroutine有关
添加一个http的接口&#xff0c;可以查看系统的状态&#xff0c;防止Goroutine不回收


查看系统状态
Nginx 配置不活跃的连接释放时间&#xff0c;防止忘记关闭的连接


使用 pprof 分析性能、耗时


3.2 使用Javascript实现webSocket客户端


3.2.1 启动并注册监听程序

js 建立连接&#xff0c;并处理连接成功、收到数据、断开连接的事件处理

ws &#61; new WebSocket("ws://127.0.0.1:8089/acc");ws.onopen &#61; function(evt) {console.log("Connection open ...");
};ws.onmessage &#61; function(evt) {console.log( "Received Message: " &#43; evt.data);data_array &#61; JSON.parse(evt.data);console.log( data_array);
};ws.onclose &#61; function(evt) {console.log("Connection closed.");
};

3.2.2 发送数据

需要注意:连接建立成功以后才可以发送数据
建立连接以后由客户端向服务器发送数据示例

登录:
ws.send(&#39;{"seq":"2323","cmd":"login","data":{"userId":"11","appId":101}}&#39;);心跳:
ws.send(&#39;{"seq":"2324","cmd":"heartbeat","data":{}}&#39;);ping 查看服务是否正常:
ws.send(&#39;{"seq":"2325","cmd":"ping","data":{}}&#39;);关闭连接:
ws.close();

4、goWebSocket 项目

4.1 项目说明

本项目是基于webSocket实现的分布式IM系统

客户端随机分配用户名&#xff0c;所有人进入一个聊天室&#xff0c;实现群聊的功能

单台机器(24核128G内存)支持百万客户端连接

支持水平部署&#xff0c;部署的机器之间可以相互通讯

项目架构图

4.2 项目依赖

本项目只需要使用 redis 和 golang
本项目使用govendor管理依赖&#xff0c;克隆本项目就可以直接使用
# 主要使用到的包

github.com/gin-gonic/gin&#64;v1.4.0
github.com/go-redis/redis
github.com/gorilla/websocket
github.com/spf13/viper
google.golang.org/grpc
github.com/golang/protobuf

4.3 项目启动

克隆项目

git clone git&#64;github.com:link1st/gowebsocket.git
# 或
git clone https://github.com/link1st/gowebsocket.git

修改项目配置

cd gowebsocket
cd config
mv app.yaml.example app.yaml
# 修改项目监听端口&#xff0c;redis连接等(默认127.0.0.1:3306)
vim app.yaml
# 返回项目目录&#xff0c;为以后启动做准备
cd ..

配置文件说明

app:logFile: log/gin.log # 日志文件位置httpPort: 8080 # http端口webSocketPort: 8089 # webSocket端口rpcPort: 9001 # 分布式部署程序内部通讯端口httpUrl: 127.0.0.1:8080webSocketUrl:  127.0.0.1:8089redis:addr: "localhost:6379"password: ""DB: 0poolSize: 30minIdleConns: 30

启动项目

go run main.go

进入IM聊天地址
http://127.0.0.1:8080/home/index
到这里&#xff0c;就可以体验到基于webSocket的IM系统

5、webSocket项目Nginx配置

5.1 为什么要配置Nginx

使用nginx实现内外网分离&#xff0c;对外只暴露Nginx的Ip(一般的互联网企业会在nginx之前加一层LVS做负载均衡)&#xff0c;减少入侵的可能
使用Nginx可以利用Nginx的负载功能&#xff0c;前端再使用的时候只需要连接固定的域名&#xff0c;通过Nginx将流量分发了到不同的机器
同时我们也可以使用Nginx的不同的负载策略(轮询、weight、ip_hash)

5.2 nginx配置

使用域名 im.91vh.com 为示例&#xff0c;参考配置
一级目录im.91vh.com/acc 是给webSocket使用&#xff0c;是用nginx stream转发功能(nginx 1.3.31 开始支持&#xff0c;使用Tengine配置也是相同的)&#xff0c;转发到golang 8089 端口处理
其它目录是给HTTP使用&#xff0c;转发到golang 8080 端口处理

upstream  go-im
{server 127.0.0.1:8080 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;keepalive 16;
}upstream  go-acc
{server 127.0.0.1:8089 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;keepalive 16;
}server {listen       80 ;server_name  im.91vh.com;index index.html index.htm ;location /acc {proxy_set_header Host $host;proxy_pass http://go-acc;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header Connection "";proxy_redirect off;proxy_intercept_errors on;client_max_body_size 10m;}location /{proxy_set_header Host $host;proxy_pass http://go-im;proxy_http_version 1.1;proxy_set_header Connection "";proxy_redirect off;proxy_intercept_errors on;client_max_body_size 30m;}access_log  /link/log/nginx/access/im.log;error_log   /link/log/nginx/access/im.error.log;
}

5.3 问题处理

运行nginx测试命令&#xff0c;查看配置文件是否正确

/link/server/tengine/sbin/nginx -t

如果出现错误

nginx: [emerg] unknown "connection_upgrade" variable
configuration file /link/server/tengine/conf/nginx.conf test failed

处理方法
nginx.com添加

http{fastcgi_temp_file_write_size 128k;
..... # 需要添加的内容#support websocketmap $http_upgrade $connection_upgrade {default upgrade;&#39;&#39;      close;}.....gzip on;}

原因:Nginx代理webSocket的时候就会遇到Nginx的设计问题 End-to-end and Hop-by-hop Headers

6、压测

6.1 Linux内核优化

设置文件打开句柄数

ulimit -n 1000000

设置sockets连接参数

vim /etc/sysctl.conf
net.ipv4.tcp_tw_reuse &#61; 1
net.ipv4.tcp_tw_recycle &#61; 0

6.2 压测准备

待压测&#xff0c;如果大家有压测的结果欢迎补充

后续会出专门的教程,从申请机器、写压测用例、内核优化、得出压测数据

关于压测请移步 go-stress-testing&#xff0c;从申请机器开始&#xff0c;优化内核&#xff0c;部署项目压测&#xff0c;解释压测的原理

6.3 压测数据


  • 项目在实际使用的时候&#xff0c;每个连接约占 24Kb内存&#xff0c;一个Goroutine 约占11kb
  • 支持百万连接需要22G内存

在线用户数 cpu内存I/O net.out
1W
10W
100W

7、如何基于webSocket实现一个分布式Im

7.1 说明

参考本项目源码

gowebsocket v1.0.0 单机版Im系统

gowebsocket v2.0.0 分布式Im系统

为了方便演示&#xff0c;IM系统和webSocket(acc)系统合并在一个系统中

IM系统接口:
获取全部在线的用户&#xff0c;查询单前服务的全部用户&#43;集群中服务的全部用户
发送消息&#xff0c;这里采用的是http接口发送(微信网页版发送消息也是http接口)&#xff0c;这里考虑主要是两点:
1.服务分离&#xff0c;让acc系统尽量的简单一点&#xff0c;不掺杂其它业务逻辑
2.发送消息是走http接口&#xff0c;不使用webSocket连接&#xff0c;才用收和发送数据分离的方式&#xff0c;可以加快收发数据的效率

7.2 架构

项目启动注册和用户连接时序图


其它系统(IM、任务)向webSocket(acc)系统连接的用户发送消息时序图

7.3 分布式系统部署
  • 用水平部署两个项目(gowebsocket和gowebsocket1)演示分部署
  • 项目之间如何相互通讯:项目启动以后将项目Ip、rpcPort注册到redis中&#xff0c;让其它项目可以发现&#xff0c;需要通讯的时候使用gRpc进行通讯
  • gowebsocket

# app.yaml 配置文件信息
app:logFile: log/gin.loghttpPort: 8080webSocketPort: 8089rpcPort: 9001httpUrl: im.91vh.comwebSocketUrl:  im.91vh.com# 在启动项目
go run main.go 

  • gowebsocket1

# 将第一个项目拷贝一份
cp -rf gowebsocket gowebsocket1
# app.yaml 修改配置文件
app:logFile: log/gin.loghttpPort: 8081webSocketPort: 8090rpcPort: 9002httpUrl: im.91vh.comwebSocketUrl:  im.91vh.com# 在启动第二个项目
go run main.go 

  • Nginx配置

在之前Nginx配置项中添加第二台机器的Ip和端口

upstream  go-im
{server 127.0.0.1:8080 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;server 127.0.0.1:8081 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;keepalive 16;
}upstream  go-acc
{server 127.0.0.1:8089 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;server 127.0.0.1:8090 weight&#61;1 max_fails&#61;2 fail_timeout&#61;10s;keepalive 16;
}

  • 配置完成以后重启Nginx
  • 重启以后请求&#xff0c;验证是否符合预期:

查看请求是否落在两个项目上
实验两个用户分别连接不同的项目(gowebsocket和gowebsocket1)是否也可以相互发送消息

  • 关于分布式部署

本项目只是演示了这个项目如何分布式部署&#xff0c;以及分布式部署以后模块如何进行相互通讯
完全解决系统没有单点的故障&#xff0c;还需 Nginx集群、redis cluster等

8、回顾和反思

8.1 在其它系统应用


  • 本系统设计的初衷就是:和客户端保持一个长连接、对外部系统两个接口(查询用户是否在线、给在线的用户推送消息)&#xff0c;实现业务的分离
  • 只有和业务分离可&#xff0c;才可以供多个业务使用&#xff0c;而不是每个业务都建立一个长连接

8.2 已经实现的功能


  • gin log日志(请求日志&#43;debug日志)
  • 读取配置文件 完成
  • 定时脚本&#xff0c;清理过期未心跳连接 完成
  • http接口&#xff0c;获取登录、连接数量 完成
  • http接口&#xff0c;发送push、查询有多少人在线 完成
  • grpc 程序内部通讯&#xff0c;发送消息 完成
  • appIds 一个用户在多个平台登录
  • 界面&#xff0c;把所有在线的人拉倒一个群里面&#xff0c;发送消息 完成
  • 单聊、群聊 完成
  • 实现分布式&#xff0c;水平扩张 完成
  • 压测脚本
  • 文档整理
  • 文档目录、百万长连接的实现、为什么要实现一个IM、怎么实现一个Im
  • 架构图以及扩展


IM实现细节:

  • 定义文本消息结构 完成
  • html发送文本消息 完成
  • 接口接收文本消息并发送给全体 完成
  • html接收到消息 显示到界面 完成
  • 界面优化 需要持续优化
  • 有人加入以后广播全体 完成
  • 定义加入聊天室的消息结构 完成
  • 引入机器人 待定

8.3 需要完善、优化


  • 登录&#xff0c;使用微信登录 获取昵称、头像等
  • 有账号系统、资料系统
  • 界面优化、适配手机端
  • 消息 文本消息(支持表情)、图片、语音、视频消息
  • 微服务注册、发现、熔断等
  • 添加配置项&#xff0c;单台机器最大连接数量

8.4 总结


  • 虽然实现了一个分布式在聊天的IM&#xff0c;但是有很多细节没有处理(登录没有鉴权、界面还待优化等)&#xff0c;但是可以通过这个示例可以了解到:通过WebSocket解决很多业务上需求
  • 本文虽然号称单台机器能有百万长连接(内存上能满足)&#xff0c;但是实际在场景远比这个复杂(cpu有些压力)&#xff0c;当然了如果你有这么大的业务量可以购买更多的机器更好的去支撑你的业务&#xff0c;本程序只是演示如何在实际工作用使用webSocket.
  • 参考本文&#xff0c;你可以实现出来符合你需要的程序

9、参考文献

WebSocket轻松单台服务器5w并发jmeter实测_weixin_34379433的博客-CSDN博客  nginx LVS 的 DR模式实现 nginx 瓶颈突破 2^16 连接数限制

维基百科 WebSocket

阮一峰 WebSocket教程

WebSocket协议&#xff1a;5分钟从入门到精通

go-stress-testing 单台机器100w连接压测实战

github 搜:link1st 查看项目 gowebsocket

https://github.com/link1st/gowebsocket


推荐阅读
  • 简答题(每题5分):1、label标签是什么,for和accesskey属性有什么用?label标签是一种常见 ... [详细]
  • HTTP协议之总结展望篇
    文章目录HTTP2HTTP2内核HTTP3Nginx:高性能的Web服务器OpenResty:更灵活的Web服务器网络应用防火墙(WAF)CDN ... [详细]
  • ASP.NET CORE 简介
    ASP.NETCore是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用。使用ASP.NETCore,您可以:生成Web ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • Linux下部署Symfoy2对app/cache和app/logs目录的权限设置,symfoy2logs
    php教程|php手册xml文件php教程-php手册Linux下部署Symfoy2对appcache和applogs目录的权限设置,symfoy2logs黑色记事本源码,vsco ... [详细]
  • 微服务之总体架构篇
    一、单体架构存在的问题缺点:1、难以维护:当单体应用业务不断迭代后代码量非常臃肿,模整个项目非常复杂,每次更改代码都可能带来新的bug;2、部署项目麻烦:庞大之后项目部署效率 ... [详细]
  • 在这分布式系统架构盛行的时代,很多互联网大佬公司开源出自己的分布式RPC系统框架,例如:阿里的dubbo,谷歌的gRPC,apache的Thrift。而在我们公司一直都在推荐使用d ... [详细]
  • golang反射,golang反射性能
    本文目录一览:1、关于反射2、尝试用golan ... [详细]
  • 如果说以比特币为代表的货币区块链技术为1.0,以以太坊为代表的合同区块链技术为2.0,那么实现了完备的权限控制和安全保障的Hyperledger项目毫无疑问代表着区块链技术3.0 ... [详细]
  • 基于.NET Core框架nacos的简单应用
    什么是Nacos?服务(Service)是Nacos世界的一等公民。Nacos支持 ... [详细]
  • 关于golangvue的信息
    本文目录一览:1、开发beego和vue,哪个更难一点 ... [详细]
  • 本文整理了Java中org.hamcrest.core.IsEqual.areArraysEqual()方法的一些代码示例,展示了IsEqual.areAr ... [详细]
author-avatar
jfgkj6454_478
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有