Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Request-ID plugin add snowflake algorithm #4559

Merged
merged 38 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2d74653
feat Request-ID plugin adds Snowflake algorithm
dickens7 Jul 7, 2021
ce66bfb
fixed no newline at end of file
dickens7 Jul 7, 2021
c3529cc
feat Request-ID plugin adds Snowflake algorithm
dickens7 Jul 7, 2021
1ca0f46
sytle line is to long
dickens7 Jul 7, 2021
c239679
fixed default config
dickens7 Jul 7, 2021
da2370e
fixed conflict
dickens7 Jul 7, 2021
f2f9cc0
fixed lint
dickens7 Jul 7, 2021
1b8565e
fixed ci
dickens7 Jul 8, 2021
bdf46f4
fixed test case
dickens7 Jul 8, 2021
d095301
fixed no newline at end of file
dickens7 Jul 8, 2021
1245d9f
optimied: Split function
dickens7 Jul 10, 2021
89cdec0
optimized: use apisix.timers
dickens7 Jul 10, 2021
428cb08
remove: api
dickens7 Jul 10, 2021
4f1717f
feat: support timestamp delta offset configuration
dickens7 Jul 11, 2021
7c6bfa7
docs: add snowflake algorithm instructions
dickens7 Jul 11, 2021
e0284a2
rename worker_number to data_machine
dickens7 Jul 13, 2021
fe8568b
fixed workerid and datacenter_id computation error
dickens7 Jul 13, 2021
90e3544
etcd grant add exception validation
dickens7 Jul 13, 2021
cb07351
fix: goto may causes the id > max_number
dickens7 Jul 13, 2021
61ed66e
chore: fix name comment
dickens7 Jul 13, 2021
b97bb94
feat: limit data_machine_bits maximun
dickens7 Jul 13, 2021
3c2d4e7
chore: use the old version lua-snowflake first
dickens7 Jul 13, 2021
cd39e27
docs: optimzie 'data_machine_bits' describe
dickens7 Jul 13, 2021
77dd441
docs: fix end of line
dickens7 Jul 13, 2021
5941f62
docs: fix markdownlint
dickens7 Jul 13, 2021
49aae46
fix: plugin destroy unregister timer
dickens7 Jul 20, 2021
ea7c7c6
chore: remove delta offset
dickens7 Jul 20, 2021
82e6250
docs: added English doc
dickens7 Jul 20, 2021
c487aeb
fix: markdownlint
dickens7 Jul 20, 2021
9e5e70a
fixcheck if the timer is registered
dickens7 Jul 22, 2021
756ba19
fix: etcd_cli:grant error continue
dickens7 Jul 22, 2021
9611604
Merge branch 'master' into feat-add-snowflake
dickens7 Jul 26, 2021
8e7d777
fix: infinite loop
dickens7 Jul 27, 2021
99bc9f3
docs: fix describe the error
dickens7 Jul 27, 2021
b777bc4
Merge branch 'feat-add-snowflake' of https://github.com/dickens7/apis…
dickens7 Jul 27, 2021
9894d69
docs: fixed attributes
dickens7 Jul 27, 2021
72a7b0b
fix: have default values, set optional
dickens7 Jul 28, 2021
fd3dc4f
Merge branch 'master' into feat-add-snowflake
dickens7 Aug 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 178 additions & 9 deletions apisix/plugins/request-id.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,163 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local core = require("apisix.core")
local plugin_name = "request-id"
local ngx = ngx
local uuid = require("resty.jit-uuid")

local ngx = ngx
local bit = require("bit")
local core = require("apisix.core")
local snowflake = require("snowflake")
local uuid = require("resty.jit-uuid")
local process = require("ngx.process")
local tostring = tostring
local math_pow = math.pow

local plugin_name = "request-id"

local worker_number = nil
local snowflake_init = nil

local attr = nil

local schema = {
type = "object",
properties = {
header_name = {type = "string", default = "X-Request-Id"},
include_in_response = {type = "boolean", default = true}
include_in_response = {type = "boolean", default = true},
algorithm = {type = "string", enum = {"uuid", "snowflake"}, default = "uuid"}
}
}

local attr_schema = {
type = "object",
properties = {
snowflake = {
type = "object",
properties = {
enable = {type = "boolean"},
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
snowflake_epoc = {type = "integer", minimum = 1, default = 1609459200000},
node_id_bits = {type = "integer", minimum = 1, default = 5},
sequence_bits = {type = "integer", minimum = 1, default = 10},
datacenter_id_bits = {type = "integer", minimum = 1, default = 5},
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
worker_number_ttl = {type = "integer", minimum = 1, default = 30},
worker_number_interval = {type = "integer", minimum = 1, default = 10}
}
}
}
}

local _M = {
version = 0.1,
priority = 11010,
name = plugin_name,
schema = schema,
schema = schema
}


function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

local function gen_worker_number(max_number)
if worker_number == nil then
local etcd_cli, prefix = core.etcd.new()
local res, _ = etcd_cli:grant(attr.snowflake.worker_number_ttl)

local prefix = prefix .. "/plugins/request-id/snowflake/"
local uuid = uuid.generate_v4()
local id = 1
while (id <= max_number) do
::continue::
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
local _, err1 = etcd_cli:setnx(prefix .. tostring(id), uuid)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have to write ETCD so that we can write the uuid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of writing etcd here is to generate DataMachineId for snowflake. The value is uuid because etcd_cli:setnx still returns succeeded even if the key exists. UUID is used for verification after etcd_cli:setnx.

local res2, err2 = etcd_cli:get(prefix .. tostring(id))

if err1 or err2 or res2.body.kvs[1].value ~= uuid then
core.log.notice("worker_number " .. id .. " is not available")
id = id + 1
else
worker_number = id

local _, err3 =
etcd_cli:set(
prefix .. tostring(id),
uuid,
{
prev_kv = true,
lease = res.body.ID
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
}
)

if err3 then
id = id + 1
etcd_cli:delete(prefix .. tostring(id))
core.log.error("set worker_number " .. id .. " lease error: " .. err3)
goto continue
end

local handler = function(premature, etcd_cli, lease_id)
local _, err4 = etcd_cli:keepalive(lease_id)
if err4 then
snowflake_init = nil
worker_number = nil
core.log.error("snowflake worker_number lease faild.")
end
core.log.info("snowflake worker_number lease success.")
end
ngx.timer.every(attr.snowflake.worker_number_interval,
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
handler, etcd_cli, res.body.ID)

core.log.notice("snowflake worker_number: " .. id)
break
end
end

if worker_number == nil then
core.log.error("No worker_number is not available")
return nil
end
end
return worker_number
end

local function split_worker_number(worker_number, node_id_bits, datacenter_id_bits)
local num = bit.tobit(worker_number)
local worker_id = bit.band(num, math_pow(2, node_id_bits) - 1) + 1
num = bit.rshift(num, node_id_bits)
local datacenter_id = bit.band(num, math_pow(2, datacenter_id_bits) - 1) + 1
return worker_id, datacenter_id
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
end

local function next_id()
if snowflake_init == nil then
local max_number = math_pow(2, (attr.snowflake.node_id_bits +
attr.snowflake.datacenter_id_bits))
worker_number = gen_worker_number(max_number)
if worker_number == nil then
return ""
end
local worker_id, datacenter_id = split_worker_number(worker_number,
attr.snowflake.node_id_bits, attr.snowflake.datacenter_id_bits)
core.log.notice("snowflake init datacenter_id: " ..
datacenter_id .. " worker_id: " .. worker_id)
snowflake.init(
worker_id,
datacenter_id,
attr.snowflake.snowflake_epoc,
attr.snowflake.node_id_bits,
attr.snowflake.datacenter_id_bits,
attr.snowflake.sequence_bits
)
snowflake_init = true
end
return snowflake:next_id()
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we put them under utils? Snowflake ID generating should also be used elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's implement this in the next pr

function _M.rewrite(conf, ctx)
local headers = ngx.req.get_headers()
local uuid_val = uuid()
local uuid_val
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
if conf.algorithm == "uuid" then
uuid_val = uuid()
else
uuid_val = next_id()
end
if not headers[conf.header_name] then
core.request.set_header(ctx, conf.header_name, uuid_val)
end
Expand All @@ -53,7 +180,6 @@ function _M.rewrite(conf, ctx)
end
end


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove this blank?

function _M.header_filter(conf, ctx)
if not conf.include_in_response then
return
Expand All @@ -65,4 +191,47 @@ function _M.header_filter(conf, ctx)
end
end

function _M.init()
local local_conf = core.config.local_conf()
attr = core.table.try_read_attr(local_conf, "plugin_attr", plugin_name)
local ok, err = core.schema.check(attr_schema, attr)
if not ok then
core.log.error("failed to check the plugin_attr[", plugin_name, "]", ": ", err)
return
end
if attr.snowflake.enable then
if process.type() == "worker" then
ngx.timer.at(0, next_id)
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
end
end
end

function _M.api()
dickens7 marked this conversation as resolved.
Show resolved Hide resolved
local api = {
{
methods = {"GET"},
uri = "/apisix/plugin/request_id/uuid",
handler = uuid
}
}

local local_conf = core.config.local_conf()
attr = core.table.try_read_attr(local_conf, "plugin_attr", plugin_name)
local ok, err = core.schema.check(attr_schema, attr)
if not ok then
core.log.error("failed to check the plugin_attr[", plugin_name, "]", ": ", err)
return
end

if attr.snowflake.enable then
core.table.insert(api, {
methods = {"GET"},
uri = "/apisix/plugin/request_id/snowflake",
handler = next_id
})
end

return api
end

return _M
9 changes: 9 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,12 @@ plugin_attr:
report_ttl: 3600 # live time for server info in etcd (unit: second)
dubbo-proxy:
upstream_multiplex_count: 32
request-id:
snowflake:
enable: false
snowflake_epoc: 1609459200000
node_id_bits: 5
sequence_bits: 5
datacenter_id_bits: 10
worker_number_ttl: 30
worker_number_interval: 10
1 change: 1 addition & 0 deletions rockspec/apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies = {
"lua-resty-consul = 0.3-2",
"penlight = 1.9.2-1",
"ext-plugin-proto = 0.1.1",
"api7-snowflake = 2.0-1",
}

build = {
Expand Down
Loading