《用OpenResty搭建高性能服務端》筆記

2021-02-26 飛鴻影的博客
概要

《用OpenResty搭建高性能服務端》是 OpenResty系列課程中的入門課程,主講人:溫銘老師。課程分為10個章節,側重於 OpenResty的基本概念和主要特點的介紹,包括它的指令、 nginx_lua API、緩存、如何連接資料庫、執行階段等,並通過幾個實際的操作和代碼片段,告訴大家學習中如何搭建開發、測試環境,如何調試、查找和解決問題。

視頻播放地址:https://study.163.com/course/introduction.htm?courseId=1520005

課程目錄一覽:

個人評價:評分滿分。內容由淺入深,思路清晰,內容組織有序,容易上手,為初學者打開了一扇學習的大門。非常不錯的分享。學完後需再配合 《OpenResty最佳實踐》 + 官方文檔 進行系統學習。

下面是學習筆記,內容主要是以老師的講解為主,加上部分自己補充或理解的內容。

本文環境:

$ uname -a

Linux ba2f3eedf7df 4.4.111-boot2docker #1 SMP Thu Jan 11 16:25:31 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/redhat-release

CentOS release 6.8 (Final)

$ /usr/local/openresty/bin/openresty -v

nginx version: openresty/1.13.6.2

$ /usr/local/openresty/luajit/bin/luajit -v

LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

OpenResty 簡介

OpenResty® 是一個基於 Nginx 與 Lua 的高性能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高並發、擴展性極高的動態 Web 應用、Web 服務和動態網關。

OpenResty 基於 Nginx開發,可以簡單認為是 Nginx + lua-nginx-module的組合版。

官網:https://openresty.org/cn/官方文檔:https://github.com/openresty/lua-nginx-module#version

高性能服務端兩個重要要素:需要支持緩存,語言層面要支持異步非堵塞。

緩存速度上,內存 > SSD > 機械磁碟;本機 > 網絡 ; 進程內 > 進程間 。異步非阻塞指的是事件驅動方式(事件完成後再通知)。

OpenResty 包含的技術:

OpenResty 本質上是將 LuaJIT 的虛擬機嵌入到 Nginx的worker中,所以效率特別高,在性能上, OpenResty 接近或超過 Nginx c module:

OpenResty已經顛覆了高性能服務端的開發模式。

OpenResty與市面上其他語言對比:

node.js:第一門將異步非阻塞特性放入自己語言中的,前端同學可以快速切入。但是 node.js 用回調(callback)實現異步非阻塞,代碼寫起來比較麻煩。

Python:3.4之後加入了異步的支持,比如異步io和aiohttp;3.5引入了協程。缺點是版本跨度大,因為很多人還是使用2.7。

Golang:最近幾年非常火。缺點:代碼寫法上需要使用go關鍵字;線上熱調試不方便( SystemTap 提供了有限的支持)。

Hello WorldOpenResty安裝

以 CentOS 為例:

mkdir /opt && cd /opt

# download openresty

wget https://openresty.org/download/openresty-1.13.6.2.tar.gz

tar zxvf openresty-1.13.6.2.tar.gz

cd openresty-1.13.6.2

# configure

./configure --prefix=/usr/local/openresty -j4

make -j4 && make install

其中 源碼包可以到 https://openresty.org/cn/download.html 該頁面獲取。 -j4表示使用4核。 configure那一步還可以指定各種參數:

./configure --prefix=/usr/local/openresty \

--with-luajit \

--without-http_redis2_module \

--with-http_iconv_module \

--with-http_postgres_module

使用 ./configure--help 查看更多的選項。

其它系統環境上安裝可以參考 https://openresty.org/cn/installation.html 。

其實安裝 OpenResty 和安裝 Nginx 是類似的,因為 OpenResty 是基於 Nginx 開發的。

如果已經安裝了 Nginx,又想使用 OpenResty 的功能,可以參考 《Nginx編譯安裝Lua》:https://www.cnblogs.com/52fhy/p/10164553.html 一文安裝 lua-nginx-module模塊即可。

第一個程序

修改 /usr/local/openresty/nginx/conf/nginx.conf:

worker_processes 1;

error_log logs/error.log;

events {

worker_connections 1024;

}

http {

server {

listen 8080;

location /hello {

default_type text/html;

content_by_lua '

ngx.say("<p>hello, world</p>")

';

}

}

}

把默認的 80埠改為 8080,新增 /hello部分。

其中 content_by_lua便是 OpenResty 提供的指令,在官方文檔可以搜索到:

現在我們啟動OpenResty:

/usr/local/openresty/nginx/sbin/nginx

啟動成功後,查看效果:

curl http://127.0.0.1:8080/hello

<p>hello, world</p>

說明成功運行了。

知識點:1、 content_by_lua:返回的內容使用 lua 代碼。2、 content_by_lua_file:讀取lua文件裡的 lua 代碼。3、默認情況下,修改Lua代碼,需要 reload OpenResty服務才會生效。可以修改 lua_code_cache為 off,作用域: http, server, location, location if。請勿在生產環境裡開啟。

測試1:使用 content_by_lua_file

cd /usr/local/openresty

mkdir nginx/conf/lua

vim nginx/conf/lua/hello.lua

內容為:

ngx.say("<p>hello, lua world</p>")

然後修改 nginx.conf:

location /hello {

default_type text/html;

content_by_lua_file conf/lua/hello.lua;

}

重啟 OpenResty:

$ ./nginx/sbin/nginx -s reload

啟動成功後,查看效果:

$ curl http://127.0.0.1:8080/hello

<p>hello, lua world</p>

測試2:關閉 lua_code_cache:根據 lua_code_cache作用域,我們可以在server塊加上:

lua_code_cache off;

location /hello {

default_type text/html;

content_by_lua_file conf/lua/hello.lua;

}

然後重啟:

$ ./nginx/sbin/nginx -s reload

nginx: [alert] lua_code_cache is off; this will hurt performance in /usr/local/openresty/nginx/conf/nginx.conf:43

提示說 lua_code_cache關閉後影響性能。我們再次修改 nginx/conf/lua/hello.lua的代碼,保存後就會生效,無需 reload server。

OpenResty 入門

這節使用 ngx_lua api完成一個小功能。

lua代碼:
nginx/conf/lua/getrandomstring.lua

-- 實現隨機字符串

local args = ngx.req.get_uri_args()

local salt = args.salt

if not salt then

ngx.exit(ngx.HTTP_BAD_REQUEST)

end

local str = ngx.md5(ngx.time() .. salt)

ngx.say(str)

修改 nginx.conf ,新增:

location /get_random_string {

content_by_lua_file conf/lua/get_random_string.lua;

}

由於修改了 nginx.conf ,需要reload OpenResty 服務。然後,我們訪問服務:

$ curl http://127.0.0.1:8080/get_random_string?salt=2

2d8231ff301ab0ce8b95c7e4c2c59574

$ curl http://127.0.0.1:8080/get_random_string?salt=2

c145db4ec45a6bf792ac30ed4246c563

說明:1、 ngx.req.get_uri_args()用於獲取URI請求參數。2、 ngx.HTTP_BAD_REQUEST為ngx常量,指的是400。代碼裡儘量使用常量。3、 ngx.time()用於獲取時間戳,是帶有緩存的。與Lua的日期庫不同,不涉及系統調用。儘量使用Ngx給出的方法,以免發生性能問題。4、 ngx.md5()用於生成md5值。5、如果代碼裡有語法錯誤,我們可以通過nginx 的 error.log裡看到,默認文件是 nginx/logs/error.log。

再次提醒大家,做 OpenResty 開發,lua-nginx-module 的文檔是你的首選,Lua 語言的庫都是同步阻塞的,用的時候要三思。也就是說,儘量使用 ngx_lua提供的api,而不是使用 Lua 本身的。例如 ngx.sleep()與 lua提供的sleep,前者不會造成阻塞,後者是會阻塞的,詳見:sleep · OpenResty最佳實踐 。

ngx_lua API介紹

本節主要是帶著大家簡單的過一下常用的ngx_lua API。

ngx_lua 有60多個指令(Directive),140多個 API(截止到2019-3-26)。

指令 是 ngx_lua 提供給Nginx調用的方法,與 Nginx自帶的 location、 rewrite等是一個級別的。指令有自己的作用域,例如: content_by_lua_file只能作用於 location和 locationif裡面:

API 是指ngx_lua基於lua代碼實現的一系列方法或常量,遵循 lua的語法規則。只能在lua代碼塊或者lua文件裡使用。

例如:

content_by_lua '

ngx.say("<p>hello, world</p>")

';

其中 content_by_lua是指令,作用於 location塊; ngx.say()是 ngx_lua 提供的API。

在官方文檔上可以找到指令及API所在的位置:

下面,我們使用 ngx_lua完成另外一個小功能:實現base64的解碼並重新json編碼輸出。代碼裡會用到一些指令和API。

lua代碼:
nginx/conf/lua/decode_info.lua

-- 實現base64的解碼並重新json編碼輸出

local json = require "cjson"

ngx.req.read_body()

local args = ngx.req.get_post_args()

if not args or not args.info then

ngx.exit(ngx.HTTP_BAD_REQUEST)

end

local client_ip = ngx.var.remote_var or '127.0.0.1'

local user_agnet = ngx.req.get_headers()['user_agent'] or ''

local info = ngx.decode_base64(args.info)

local res = {}

res.client_ip = client_ip

res.user_agnet = user_agnet

res.info = info

ngx.say(json.encode(res))

修改 nginx.conf ,新增:

location /decode_info {

content_by_lua_file conf/lua/decode_info.lua;

}

由於修改了 nginx.conf ,需要 reload OpenResty 服務。然後,我們訪問服務:

$ php -r "echo base64_encode('test');"

dGVzdA==

$ curl -XPOST -d "info=dGVzdA==" http://127.0.0.1:8080/decode_info

{"user_agnet":"curl\/7.19.7","client_ip":"127.0.0.1","info":"test"}

說明:1、 require是 lua 裡面引入其他庫的關鍵字。這裡引入的 cjson。2、當我們要讀取 http裡的post數據的時候,就需要使用 ngx.req.read_body()。該API同步讀取客戶端請求主體而不阻塞Nginx事件循環。3、 ngx.req.get_post_args() 用於獲取post請求數據。4、 ngx.var.remote_var實際是獲取的nginx裡的變量 remote_var。也就是說, ngx.var.xxx實際是獲取的nginx裡的變量 xxx。例如:

nginx變量詳見:[Alphabetical index of variables}(http://nginx.org/en/docs/varindex.html)。 ngx_lua ngx.var API詳見:ngx.var.VARIABLE。5、 ngx.req.get_headers() 用於讀取nginx的header參數。返回的是lua table。6、 ngx.decode_base64()用於 base64字符串解碼。對應的編碼API是 ngx.encode_base64()。

防盜版聲明:本文系原創文章,發布於公眾號 飛鴻影的博客(fhyblog)及博客園,轉載需作者同意。

連接資料庫

連接資料庫我們需要使用到ngx_lua的第三方庫:

這兩個庫都是基於cosocket實現的,特點是異步非阻塞。代碼風格是同步的寫法。更多第三方庫詳見:See Also 。

連接 MySQL

lua代碼:
nginx/conf/lua/test_mysql.lua

local mysql = require "resty.mysql"

local db, err = mysql:new()

if not db then

ngx.say("failed to instantiate mysql: ", err)

return

end

db:set_timeout(1000) -- 1 sec

local ok, err, errcode, sqlstate = db:connect{

host = "127.0.0.1",

port = 3306,

database = "ngx_test",

user = "ngx_test",

password = "ngx_test",

charset = "utf8",

max_packet_size = 1024 * 1024,

}

if not ok then

ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate)

return

end

-- insert

res, err, errcode, sqlstate =

db:query("insert into cats (name) "

.. "values (\'Bob\'),(\'\'),(null)")

if not res then

ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")

return

end

ngx.say(res.affected_rows, " rows inserted into table cats ",

"(last insert id: ", res.insert_id, ")")

-- run a select query, expected about 10 rows in the result set

res, err, errcode, sqlstate =

db:query("select * from cats order by id asc", 10)

if not res then

ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")

return

end

local cjson = require "cjson"

ngx.say("result: ", cjson.encode(res))

-- close connection

local ok, err = db:close()

if not ok then

ngx.say("failed to close: ", err)

return

end

修改 nginx.conf ,新增:

location /test_mysql {

content_by_lua_file conf/lua/test_mysql.lua;

}

由於修改了 nginx.conf ,需要 reload OpenResty 服務。然後,我們訪問服務:

$ http://127.0.0.1:8080/test_mysql

連接 Redis

lua代碼:
nginx/conf/lua/test_redis.lua

local redis = require "resty.redis"

local red = redis:new()

red:set_timeout(1000) -- 1 sec

local ok, err = red:connect("127.0.0.1", 6379)

if not ok then

ngx.say("failed to connect: ", err)

return

end

ok, err = red:set("dog", "an animal")

if not ok then

ngx.say("failed to set dog: ", err)

return

end

ngx.say("set result: ", ok)

local res, err = red:get("dog")

if not res then

ngx.say("failed to get dog: ", err)

return

end

if res == ngx.null then

ngx.say("dog not found.")

return

end

ngx.say("dog: ", res)

-- close the connection right away

local ok, err = red:close()

if not ok then

ngx.say("failed to close: ", err)

return

end

修改 nginx.conf ,新增:

location /test_redis {

content_by_lua_file conf/lua/test_redis.lua;

}

由於修改了 nginx.conf ,需要 reload OpenResty 服務。然後,我們訪問服務:

$ http://127.0.0.1:8080/test_redis

更多參考:redis 接口的二次封裝(簡化建連、拆連等細節) · OpenResty最佳實踐

OpenResty 緩存使用 Lua shared dict

使用的話首先需要在 nginx.conf 加上一句:

lua_shared_dict my_cache 128m;

這個 my_cache就是申請 Lua shared dict 緩存, 下面示例裡會用到。

這個緩存是 Nginx 所有 worker 之間共享的,內部使用的 LRU 算法(最近最少使用)來判斷緩存是否在內存佔滿時被清除。

function get_from_cache(key)

local cache_ngx = ngx.shared.my_cache

local value = cache_ngx:get(key)

return value

end

function set_to_cache(key, value, exptime)

if not exptime then

exptime = 0

end

local cache_ngx = ngx.shared.my_cache

local succ, err, forcible = cache_ngx:set(key, value, exptime)

return succ

end

更多支持的命令詳見:https://github.com/openresty/lua-nginx-module#ngxshareddict 。

使用 Lua LRU cache

這個 cache 是 worker 級別的,不會在 Nginx wokers 之間共享。並且,它是預先分配好 key 的數量,而 shared dict 需要自己用 key 和 value 的大小和數量,來估算需要把內存設置為多少。

官方示例:
myapp.lua

local _M = {}

-- alternatively: local lrucache = require "resty.lrucache.pureffi"

local lrucache = require "resty.lrucache"

-- we need to initialize the cache on the lua module level so that

-- it can be shared by all the requests served by each nginx worker process:

local c, err = lrucache.new(200) -- allow up to 200 items in the cache

if not c then

return error("failed to create the cache: " .. (err or "unknown"))

end

function _M.go()

c:set("dog", 32)

c:set("cat", 56)

ngx.say("dog: ", c:get("dog"))

ngx.say("cat: ", c:get("cat"))

c:set("dog", { age = 10 }, 0.1) -- expire in 0.1 sec

c:delete("dog")

c:flush_all() -- flush all the cached data

end

return _M

nginx.conf

http {

lua_package_path "/path/to/lua-resty-lrucache/lib/?.lua;;";

server {

listen 8080;

location = /t {

content_by_lua '

require("myapp").go()

';

}

}

}

更多支持的命令詳見:https://github.com/openresty/lua-resty-lrucache 。

那麼這兩個緩存 如何選擇 ?

shared.dict 使用的是共享內存,每次操作都是全局鎖,如果高並發環境,不同 worker 之間容易引起競爭。所以單個 shared.dict 的體積不能過大。 lrucache 是 worker 內使用的,由於 Nginx 是單進程方式存在,所以永遠不會觸發鎖,效率上有優勢,並且沒有 shared.dict 的體積限制,內存上也更彈性,但不同 worker 之間數據不同享,同一緩存數據可能被冗餘存儲。

你需要考慮的,一個是 Lualru cache 提供的 API 比較少,現在只有 get、set 和 delete,而 ngx shared dict還可以 add、 replace、 incr、 get_stale(在 key 過期時也可以返回之前的值)、 get_keys(獲取所有 key,雖然不推薦,但說不定你的業務需要呢);第二個是內存的佔用,由於 ngx shared dict 是 workers 之間共享的,所以在多 worker 的情況下,內存佔用比較少。

本節內容參考來自:https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/cache.html

FFI和第三方模塊FFI

FFI是 LuaJIT 中的一個擴展庫,它允許我們使用 Lua 代碼調用C語言的數據結構和函數。

FFI庫在很大程度上避免了在C中編寫繁瑣的手動 Lua/C 綁定的需要。無需學習單獨的綁定語言 - 它解析普通的C聲明!這些可以從C頭文件或參考手冊中剪切粘貼。

如何調用外部C庫函數呢?1、加載FFI庫。2、為函數添加C聲明。3、調用命名的C函數。

看一個官方提供的簡單示例:

-- test_ffi.lua

local ffi = require("ffi")

ffi.cdef[[

int printf(const char *fmt, ...);

]]

ffi.C.printf("Hello %s!", "world")

我們運行:

$ luajit test_ffi.lua

Hello world!

詳見:http://luajit.org/ext_ffi.html

增加第三方模塊

默認的 resty 庫所在位置:

$ pwd

/usr/local/openresty

$ ls lualib/

cjson.so ngx/ rds/ redis/ resty/

$ ls lualib/resty/

aes.lua limit/ md5.lua redis.lua sha384.lua upload.lua

core/ lock.lua memcached.lua sha1.lua sha512.lua upstream/

core.lua lrucache/ mysql.lua sha224.lua sha.lua websocket/

dns/ lrucache.lua random.lua sha256.lua string.lua

現在以安裝 lua-resty-http 為例:

$ cd /opt

# 下載並解壓

$ wget https://github.com/ledgetech/lua-resty-http/archive/v0.13.tar.gz && tar zxvf v0.13.tar.gz

# 複製到resty目錄即可

$ cp -r lua-resty-http-0.13/lib/resty/* /usr/local/openresty/lualib/resty/

# 查看安裝的模塊

$ cd /usr/local/openresty/lualib/resty/ && ls http*

http_headers.lua http.lua

使用示例:

local http = require "resty.http"

local httpc = http.new()

local res, err = httpc:request_uri("http://example.com/helloworld", {

method = "POST",

body = "a=1&b=2",

headers = {

["Content-Type"] = "application/x-www-form-urlencoded",

},

keepalive_timeout = 60,

keepalive_pool = 10

})

if not res then

ngx.say("failed to request: ", err)

return

end

ngx.status = res.status

for k,v in pairs(res.headers) do

--

end

ngx.say(res.body)

子查詢

子查詢只是模擬 HTTP 接口的形式, 沒有 額外的 HTTP/TCP 流量,也 沒有 IPC (進程間通信) 調用。所有工作在內部高效地在 C 語言級別完成。

子查詢只能在一個 location 裡調用其它 一個或多個 `location。

res=ngx.location.capture(uri,options?) 發起子查詢
返回一個包含四個元素的 Lua 表 ( res.status, res.header, res.body, 和 res.truncated)。 作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*。

示例:

res = ngx.location.capture(

'/foo/bar',

{ method = ngx.HTTP_POST, body = 'hello, world' }

)

res1,res2,...=ngx.location.capture_multi({{uri,options?},{uri,options?},...})發起多個並發子查詢 作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*。

示例:

res1, res2, res3 = ngx.location.capture_multi{

{ "/foo", { args = "a=3&b=4" } },

{ "/bar" },

{ "/baz", { method = ngx.HTTP_POST, body = "hello" } },

}

if res1.status == ngx.HTTP_OK then

...

end

if res2.body == "BLAH" then

...

end

作用:減少網絡請求。方便配置降級服務。

子查詢文檔參考: https://github.com/openresty/lua-nginx-module#ngxlocationcapture

執行階段

下面這個圖是 ngx_lua 各個指令的執行順序。

執行階段說明:

set_by_lua*: 流程分支處理判斷變量初始化

rewrite_by_lua*: 轉發、重定向、緩存等功能(例如特定請求代理到外網)

access_by_lua*: IP 準入、接口權限等情況集中處理(例如配合 iptable 完成簡單防火牆)

content_by_lua*: 內容生成

header_filter_by_lua*: 響應頭部過濾處理(例如添加頭部信息)

body_filter_by_lua*: 響應體過濾處理(例如完成應答內容統一成大寫)

log_by_lua*: 會話完成後本地異步完成日誌記錄(日誌可以記錄在本地,還可以同步到其他機器)

由於 Nginx 把一個請求分成了很多階段,這樣第三方模塊就可以根據自己行為,掛載到不同階段進行處理達到目的。不同的階段,有不同的處理行為,理解了他,也能更好的理解 Nginx 的設計思維。

總結與自學

1、如何自學
《OpenResty最佳實踐》
2、遇到問題怎麼辦
1) 看 nginx 的error.log
2) 疑難問題把可復現信息在官方郵件組裡反饋
3) 善用Google
4) QQ交流群

參考

1、OpenResty® - 中文官方站
https://openresty.org/cn/
2、openresty/lua-nginx-module: Embed the Power of Lua into NGINX HTTP servers
https://github.com/openresty/lua-nginx-module#version
3、FFI Library
http://luajit.org/extffi.html
4、luajit FFI簡單使用(1) - Abel's Blog - CSDN博客https://blog.csdn.net/erlanghell/article/details/52836467
5、OpenResty最佳實踐
https://moonbingbing.gitbooks.io/openresty-best-practices/

相關焦點

  • OpenResty使用ngx.time.every完成定時任務
    文檔地址:https://github.com/openresty/lua-nginx-module#ngxtimerevery參數解釋與ngx.timer.at相似。create timer task_query_web_stat#", err_task_query_web_stat) return endend開源案例https://github.com/fendoudebb/z-blog-openresty
  • 幾步命令輕鬆搭建Windows SSH服務端
    可選應用然後找到OpenSSH服務端和客戶端,安裝這兩項。安裝OpenSSH如果你喜歡簡潔的命令行的話,同樣簡單,先運行下面的命令查看一下OpenSSH功能。# 先查看一下軟體Get-WindowsCapability -Online | ?
  • 印象筆記八周年:完整布局C端B端
    同時,結合中國個人和企業用戶和場景需求,印象筆記加速產品研發進程,18個月內自主完成近400次升級發版,全面布局C端和B端的完整生態。歷經八年沉澱和2018年重組以來兩年的獨立發展,印象筆記如今已從單一的工具應用發展成為涵蓋個人、企業團隊、內容平臺、智能硬體的綜合性知識管理服務平臺,成為國人「第二大腦」的初心和三億人首屏應用的願景也被賦予了更豐富的內涵和外延。唐毅首次向媒體公開了印象筆記的年度訂閱率曲線圖。
  • OpenResty、PHP-fpm與NodeJs操作MySQL的性能對比
    agentzh:對於單行的小結果集查詢,node-mysql + node-generic-pool 相比 php-fpm + php-mysql 還是有性能優勢的,rps 高了一倍的樣子,絕對值也有差不多 1000 rps,但和 ngx_openresty
  • 印象筆記八周年:完整布局C端和B端
    同時,結合中國個人和企業用戶和場景需求,印象筆記公司加速產品研發進程,18個月內自主完成近400次升級發版,全面布局C端和B端的完整生態。成立於2012年的印象筆記,經歷了八年的歷史沉澱和2018年重組以來兩年的獨立發展,早已從單一的工具應用,發展成為涵蓋個人、企業團隊、內容平臺、智能硬體的綜合性知識管理服務平臺,成為國人「第二大腦」的初心和三億人首屏應用的願景也被賦予了更豐富的內涵和外延。
  • 適合新手:手把手教你用Go快速搭建高性能、可擴展的IM系統
    手把手教你自擼一個Andriod版簡易IM (有源碼)》《拿起鍵盤就是幹:跟我一起徒手開發一套分布式IM系統》《適合新手:從零開發一個IM服務端(基於Netty,有完整源碼)》另外:本文作者的另一篇文章,有興趣也可以關注一下:《12306搶票帶來的啟示:看我如何用Go實現百萬QPS的秒殺系統(含源碼)》。本文已同步發布於「即時通訊技術圈」公眾號.
  • 印象筆記正式推出獨立「印象團隊」App專注服務企業團隊高效辦公
    「印象團隊」基於印象筆記八年來服務知識工作者和知識驅動型團隊的經驗,通過業界領先的公有雲SaaS模式,提供豐富的多端(包括Windows,Mac,iOS,iPad,Android)客戶體驗,在全面兼容「印象筆記」的強大知識管理功能的同時,進一步為高效團隊提供「完整生命周期的團隊信息雲端管理」、「根據場景和流程靈活可定製的協作體驗」、「完善的安全及權限管理」等服務
  • 印象筆記正式推出獨立「印象團隊」App 專注服務企業團隊高效辦公
    「印象團隊」基於印象筆記八年來服務知識工作者和知識驅動型團隊的經驗,通過業界領先的公有雲SaaS模式,提供豐富的多端(包括Windows,Mac,iOS,iPad,Android)客戶體驗,在全面兼容「印象筆記」的強大知識管理功能的同時,進一步為高效團隊提供「完整生命周期的團隊信息雲端管理」、「根據場景和流程靈活可定製的協作體驗」、「完善的安全及權限管理」等服務
  • 百度文庫狀元筆記專區:搭建資源分享平臺
    近日,2015高考狀元筆記在百度文庫和百度覓題APP全網首發,面向全網用戶分享新晉狀元們的手寫學習筆記和學習經驗。立足2014年首次發布高考狀元「手寫筆記」,今年百度文庫對產品進行進一步的技術升級和內容優化使專題閱讀和使用體驗更上一層樓。
  • 網雲穿搭建minecraft(我的世界)服務端 外網遠程聯機
    之前測試過幾款內網穿透,有的需要自己搭建,很麻煩並且花費不低。有的穿透免費版外網埠不固定老是變,一遍就要重新連接遊戲,非常麻煩;最近找到了一個免費的內網穿透,網雲穿內網穿透;在了解了外網埠和域名是固定不變的,所以嘗試了一番。感覺挺適用,配置比較簡單,下載簡單配置就可以讓外網的朋友連進來一塊開擼。
  • 印象筆記八周年:完整布局C端和B端 交出滿意答卷
    同時,結合中國個人和企業用戶和場景需求,印象筆記公司加速產品研發進程,18個月內自主完成近400次升級發版,全面布局C端和B端的完整生態。更為重要的是,印象筆記始終保持專注與創新,擁有一支執行力出眾的團隊,堅持為用戶創造獨特的產品和服務體驗。
  • 印象筆記完整布局C端和B端:推印象團隊APP,全面進軍第二大腦
    同時,印象筆記結合中國個人和企業用戶和場景需求,全面布局C端和B端的完整生態。成立於2012年的印象筆記,經歷了八年的歷史沉澱和2018年重組以來兩年的獨立發展,早已從單一的工具應用,發展成為涵蓋個人、企業團隊、內容平臺、智能硬體的綜合性知識管理服務平臺。
  • 搭建websocket消息推送服務,必須要考慮的幾個問題
    這次主要從搭建websocket服務的基本功能和特性方面做一些分享,下次有機會再從構建一個高可用websocket時要面對的高並發,海量消息,集群容災,橫向擴展,以及自動化運維等方面進更多的分享。另外還有兩個目的:服務端檢測到某個客戶端遲遲沒有心跳過來可以主動關閉通道,讓它下線;客戶端檢測到某個服務端遲遲沒有響應心跳也能重連獲取一個新的連接。2.建立具有良好兼容性的客戶端SDK雖說現在主流瀏覽器都支持websocket,但在編碼中還是會遇到瀏覽器兼容性問題,而且通過websocket通信的客戶端早已不僅限於各種web瀏覽器,還包括越來越多的APP,小程序。
  • 數據安全第一,如何搭建自己的私有雲服務?
    但是目前雲盤領域的服務還是非常不穩定,比如說之前包括金山、騰訊、360等多家雲盤突然停止服務,後續再服務,也有很多付費、限速之類的問題,讓人非常擔心自己辛苦傳上去的數據。尤其是停止服務的時候,大家一起下載,網速還會變得奇慢無比,很多內容基本最後就丟了。
  • 限量版Netty純手打筆記,年薪近90W架構師花費整整一個月整理出來
    Netty 的內部實現是很複雜的,但是 Netty 提供了簡單易用的API從網絡處理代碼中解耦業務邏輯。Netty 是完全基於 NIO 實現的,所以整個 Netty 都是異步的。3.2 Netty服務端開發3.3 Netty客戶端開發3.4運行和調試3.5總結第4章TCP粘包/拆包問題的解決之道
  • 從零搭建 Spring Cloud 服務(超詳細)
    ps: 博主基於Maven+idea搭建。 另外SpringCloud需要基於springboot搭建。在客戶端負載均衡中,所有客戶端節點都維護著自己要訪問的服務端清單,而這些服務端端清單來自於服務註冊中心,比如上一章我們介紹的Eureka服務端。
  • 貴陽:搭建大數據服務平臺 助力高質量招生
    在前不久結束的貴陽市2020年義務教育招生電腦隨機派位中,來自貴陽高新區的大數據企業——貴州京師城投智慧教育產業股份有限公司,以合理滿足入學需求、科學優化學位派位為突破口,自主研發搭建了「貴陽市義務教育入學服務平臺」。
  • 用Docker 搭建Web服務環境
    開發環境只需要安裝配置一次,之後搭建相同的開發環境很簡單,只需要一條命令即可完成!這就是本文的目的,介紹 Docker 下安裝配置 Web 的服務環境,實現一次安裝配置,多次重用、跨平臺重用的功能。靈雀雲鏡像倉庫中匯集了大量來自社區的優質作品,讓用戶組合、復用容器化微服務,輕鬆搭建新一代雲端應用。