rebar3 服务器
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
.rebar3
|
||||
_*
|
||||
.eunit
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
*.swp
|
||||
*.swo
|
||||
.erlang.cookie
|
||||
ebin
|
||||
log
|
||||
erl_crash.dump
|
||||
.rebar
|
||||
logs
|
||||
_build
|
||||
.idea
|
||||
*.iml
|
||||
rebar3.crashdump
|
||||
*~
|
||||
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2021, Anonymous <anonymous@example.org>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
**1、ms_cache服务**
|
||||
ms_cache是居于第三方mysql驱动开发的缓存功能;支持游戏节点内部署,做为游戏服的一部分;也支持独立部署,作为数据中心使用。
|
||||
ms_cache使用会涉及到三个比较重要的东西
|
||||
1、ms_cache的application的配置,指定ms_cache使用的数据库地址、类型、运作方式等
|
||||
2、rebar3_cache_plugin插件工具,通过配置文件生成ms_cache缓存服务需要的适配文件
|
||||
3、rebar3_cache_plugin插件插件需要的配置文件
|
||||
|
||||
**2、ms_cache的application配置,内容格式如下**
|
||||
{ms_cache, [
|
||||
{data_node, undefined}, % 缓存节点,undefined为缓存app启动在本节点
|
||||
{cache_db_type, ets}, % 缓存在内存的储存位置 proc | ets | mnesia(mnesia暂不支持)
|
||||
{rw_mode, concurrency}, % 缓存读写模式 concurrency-并行读写(脏读脏写) serial-串行读写(经过缓存进程进行读写)
|
||||
{data_sync_mode, key}, % 缓存数据同步模式 key-同步主键(写库时需要根据key读取缓存表的数据,然后同步到数据库) data-同步数据
|
||||
{data_fmt, record}, % 缓存数据存取的格式, record | map, 默认为record
|
||||
{table_worker_count, 8}, % 每个内存表对应处理的进程数(哈希分组到各个进程)
|
||||
{db_save_time, 120000}, % (定时同步参数)缓存定期同步到数据库的时间间隔,单位毫秒;默认值 120000
|
||||
{db_save_count, 1000}, % (定量同步参数)缓存待同步数量上限,达到数量触发同步数据库操作;默认值 1000
|
||||
{db_driver, 'mysql-otp'}, % 使用的数据库驱动(当前只支持mysql-otp第三方驱动,可扩展支持mongo)
|
||||
{db_user, "xrxdzadmin"}, % 数据库用户
|
||||
{db_pass, "xrxdz@4399##"}, % 数据库密码
|
||||
{db_host, "172.16.12.111"}, % 数据库地址
|
||||
{db_port, 3306}, % 数据库端口
|
||||
{db_name, "xrxdz_test_game"}, % 数据库名
|
||||
{db_encoding, utf8}, % 数据库编码(mysql-otp驱动下没用到)
|
||||
{db_select_pool_name, select_pool_name}, % 数据库查询链接名
|
||||
{db_select_pool_size, 32}, % 数据库查询链接池初始容量
|
||||
{db_select_pool_overflow, 128}, % 数据库查询链接池可扩展容量
|
||||
{db_update_pool_name, update_pool_name}, % 数据库更新链接名(增删改都使用该链接)
|
||||
{db_update_pool_size, 10}, % 数据库更新链接池初始容量
|
||||
{db_update_pool_overflow, 0}, % 数据库更新链接池可扩展容量
|
||||
%% 启动前修复数据,通过启动一个erlang节点来处理
|
||||
{db_sync_options, #{
|
||||
create_table => true, % 是否自动创建表
|
||||
alter_table => true, % 是否自动修改表字段(修改主键的话删表重建)
|
||||
patch_run_mod => auto, % 补丁运行模式,auto-启动服务器时自动运行, {env,"ERLANG_RUN_PATCH"}-启动时系统设置了这个环境变量就运行
|
||||
patch_prefix => "patch_", % 补丁文件的前缀名
|
||||
limit => 7 % 最多检查近x条补丁,如有遗漏追加执行,外网建议设置为1
|
||||
}}
|
||||
]}
|
||||
|
||||
**3、配置文件,.table结尾,内容格式如下**
|
||||
|
||||
%% ====================================================================================================
|
||||
%% 表配置说明
|
||||
%%
|
||||
%% {表名, 字段名列表, 主键字段列表, [可选属性map结构]}
|
||||
%%
|
||||
%% {TabName, FieldList, PrimKeyList, [OptionMap]}
|
||||
%%
|
||||
%% TabName :: 数据库对应的表名,也是代码里对应的record名
|
||||
%%
|
||||
%% FieldList :: [Field] 字段列表,对应mysql数据库表的字段
|
||||
%% Field :: {Name, Type, Len, Common, Sync}
|
||||
%% Name :: 字段名
|
||||
%% Type :: 类型,int | binary | list | map | atom | boolean | tuple | any(上面所有类型都可以,数据库用blob存储)
|
||||
%% Len :: 数据库表中该字段的长度,(Len=0,Type= list | map | tuple, 则表示该字段数据库表用text来存储, binary和any则用blob来存储)
|
||||
%% Common :: 字段的说明
|
||||
%% Sync :: 是否同步到数据库表,true | false ,(当Sync=false,表示该字段是临时数据,数据库表也没有该字段)
|
||||
%%
|
||||
%% PrimKeyList :: 表主键,为空默认表的第一个字段作为主键;多个字段表示联合主键
|
||||
%%
|
||||
%% OptionMap :: 可选属性map结构,不配置则默认为#{};key对应的首个value为默认值,不存在key则为该默认值
|
||||
%% #{
|
||||
%% type => set | ordered_set, % 表类型,目前只支持set和ordered_set,参考ets用法
|
||||
%% indexes => [], % 表索引,自动创建数据库表时自动创建索引
|
||||
%% uid_field => undefined, % 唯一id字段,类似于唯一索引,undefined表示该表不需要唯一id字段(该字段主要是用来生成select max取表的最大唯一id值)
|
||||
%% pre_load => false | true, % 启动时是否全量加载数据
|
||||
%% clear_all => false | true, % 表示可否通过ms_cache:delete_all_objects方法删除表的所有数据(慎用,不重要数据可以用该字段)
|
||||
%% db_save_time => 0 | N |... % (定时同步参数)缓存定期同步到数据库的时间间隔,单位毫秒;(默认读sys.config中ms_cache配置的db_save_time)
|
||||
%% db_save_count => 0 | N |... % (定量同步参数)缓存待同步数量上限,达到数量触发同步数据库操作;(默认读sys.config中ms_cache配置的db_save_count)
|
||||
%% merge_type => 0 | 1 | 2 | 3 % 合服类型,用于生成合服sql,0-保留主从服数据(默认值) 1-保留主服数据 2-保留从服数据 3-删除主从服数据, 4-不处理(合服后处理)
|
||||
%% }.
|
||||
%% ====================================================================================================
|
||||
%% 测试表1, 单主键, 预加载
|
||||
{im_test_1,
|
||||
[
|
||||
{im_long, int, 20, "long type", true},
|
||||
{im_shot, int, 4, "shot type", true},
|
||||
{im_int, int, 11, "int type", true},
|
||||
{im_atom, int, 20, "atom type", true},
|
||||
{im_boolean, boolean, 8, "boolean type", true},
|
||||
{im_binary, binary, 64, "binary type", true},
|
||||
{im_tuple, tuple, 64, "tuple varchar type", true},
|
||||
{im_list, list, 64, "list varchar type", true},
|
||||
{im_map, map, 64, "map varchar type", true},
|
||||
{im_tuple_text, tuple, 0, "tuple text type", true},
|
||||
{im_list_text, list, 0, "list text type", true},
|
||||
{im_map_text, map, 0, "map text type", true},
|
||||
{im_any, any, 0, "any type db-type is blob", true},
|
||||
{im_index, int, 20, "index for table", true}
|
||||
],
|
||||
[im_long], #{ indexes=>[im_db_index], pre_load=>true }
|
||||
}.
|
||||
|
||||
%% 测试表2, 双主键(im_long + im_short)
|
||||
{im_test_2,
|
||||
[
|
||||
{im_long, int, 20, "long type", true},
|
||||
{im_shot, int, 4, "shot type", true},
|
||||
{im_int, int, 11, "int type", true},
|
||||
{im_atom, int, 20, "atom type", true},
|
||||
{im_boolean, boolean, 8, "boolean type", true},
|
||||
{im_binary, binary, 64, "binary type", true},
|
||||
{im_tuple, tuple, 64, "tuple varchar type", true},
|
||||
{im_list, list, 64, "list varchar type", true},
|
||||
{im_map, map, 64, "map varchar type", true},
|
||||
{im_tuple_text, tuple, 0, "tuple text type", true},
|
||||
{im_list_text, list, 0, "list text type", true},
|
||||
{im_map_text, map, 0, "map text type", true},
|
||||
{im_any, any, 0, "any type db-type is blob", true},
|
||||
{im_compound_index1,int, 20, "compound index for table", true},
|
||||
{im_compound_index2,int, 20, "compound index for table", true}
|
||||
],
|
||||
[im_long, im_shot], #{ indexes=>[{im_compound_index1, im_compound_index2}] }
|
||||
}.
|
||||
|
||||
%% 其他说明
|
||||
多key情况下,key的使用形式是tuple;所以不管是单key的字段,还是多key的每个字段,格式都不能为tuple
|
||||
index的创建,多个index用逗号隔开;复合索引用tuple表示
|
||||
|
||||
**4、rebar3_cache_plugin插件及ms_cache库在rebar.config中配置如下**
|
||||
|
||||
%% ms_cache做为第三方依赖库
|
||||
{deps, [
|
||||
mysql
|
||||
, mysql_poolboy
|
||||
, poolboy
|
||||
, ms_cache
|
||||
]}.
|
||||
|
||||
%% 缓存插件定义
|
||||
{plugins, [
|
||||
{rebar3_cache_plugin, "0.1.0"} %% 缓存插件
|
||||
]}.
|
||||
|
||||
%% cache缓存插件配置(支持配置多个app的配置)
|
||||
{cache_opts, [
|
||||
{game_server, [ % app名字
|
||||
{i, "cache"}, % 缓存配置目录
|
||||
{module_name_prefix, ""}, % 文件名前缀,""表示没有前缀名字
|
||||
{module_name_suffix, "_c"}, % 文件名后缀,""表示没有后缀名字
|
||||
{o_erl, "src/auto_gen/cache"}, % erl文件的输出目录
|
||||
{o_hrl, "include/auto_gen/cache"}, % hrl文件的输出目录
|
||||
{o_sql, "sql"}, % sql文件输出目录
|
||||
{strings_as_binaries, true},
|
||||
type_specs
|
||||
]}
|
||||
]}.
|
||||
|
||||
%% 使用命令如下,也可以配置在rebar.config自动执行
|
||||
rebar3 cache clean
|
||||
rebar3 cache compile
|
||||
|
||||
**5、常用方法调用**
|
||||
|
||||
节点内调用 | 跨节点调用
|
||||
ms_cache:lookup(Table, Key) | ms_cache:lookup(Node, Table, Key)
|
||||
ms_cache:lookup_by_key_index(Table, KeyIndex) | ms_cache:lookup_by_key_index(Node, Table, KeyIndex)
|
||||
ms_cache:update(Table, RecOrMap) | ms_cache:update(Node, Table, RecOrMap)
|
||||
ms_cache:delete(Table, Key) | ms_cache:update(Node, Table, Key)
|
||||
ms_cache:insert(Table, RecOrMap) | ms_cache:update(Node, Table, RecOrMap)
|
||||
|
||||
以下是一些常用的仅查询缓存数据的方法(忽略数据库里的数据,通常是一些预先将所有数据加载导内存的表才会用到这些功能)
|
||||
ms_cache:index_read(Table, Key, Pos) | ms_cache:index_read(Node, Table, Key, Pos)
|
||||
ms_cache:size(Table) | ms_cache:size(Node, Table)
|
||||
ms_cache:tab2list(Table) | ms_cache:tab2list(Node, Table)
|
||||
ms_cache:tab2list_foldl(Table, Fun, Acc) | ms_cache:tab2list_foldl(Node, Table, Fun, Acc)
|
||||
|
||||
以下是一些常用的仅查询数据库的方法(忽略缓存里的数据,通常是启服初始化的时候从数据库里获取数据,比如一次性读出角色表的多有名字写入ets用于业务唯一性检测)
|
||||
ms_cache:select_count(Table) | ms_cache:select_count(Node, Table)
|
||||
ms_cache:select_max(Table) | ms_cache:select_max(Node, Table)
|
||||
ms_cache:select_max(Table) | ms_cache:select_max(Node, Table)
|
||||
ms_cache:select_foldl(Table, Fun, Acc) | ms_cache:select_foldl(Node, Table, Fun, Acc)
|
||||
|
||||
ms_cache:lookup_by_key_index/2方法的使用说明
|
||||
在多key表的情况下,按照第一个key获取对应的所有数据
|
||||
在单key表的情况下,等同于 [ms_cache:lookup/2],注意返回值是个列表
|
||||
|
||||
**6、其他说明**
|
||||
|
||||
文件中定义了数据的record结构,并提供了record转map的功能;
|
||||
record生成时自动增加'_id'作为record的key
|
||||
map格式使用时,数据必需带上'_table'作为区分表的key,map该key对应的value为表名atom
|
||||
'_id' 和 '_table' 作为保留字段,在配置中不能作为数据的字段,为了便于阅读,id字段最好也别用
|
||||
|
||||
ms_cache库中包含 ms_cache 和 ms_cache_internal 模块
|
||||
早期只有ms_cache_internal,和其他模块一起组成一个独立运行的缓存应用系统;(所以ms_cache提供的方法这里基本也有)
|
||||
ms_cache是后期为了简化部分业务逻辑,提高读写性能引入的,所以ms_cache有些接口是在业务的需求下定义的,并没有那么纯粹
|
||||
ms_cache主要做的事情
|
||||
1、通过ms_cache:init(Table, Owner, Args),指定进程可以操作Table表的哪些数据,我们也将该进程称为这些数据的宿主进程
|
||||
Owner = any,表示可以操作Table表的所有数据,这里有个特殊的情况,表的key字段不能为原子any
|
||||
Owner = KeyOrIndex, 对于单key表,只能操作KeyOrIndex为主键的一条数据;对于多key表,可以操作第1个key未KeyOrIndex的多条数据
|
||||
2、Owner /= any进程字典内可以指定存储一份ms_cache:init指定的可以操作的数据;并通过Args参数,可以自行决定调用方法将数据写入缓存的时机,缓存的数据能否被手动移除
|
||||
3、宿主进程可以实时访问最新的数据,非宿主进程只能访问缓存ets的数据,是否需要最新取决于Args参数的设定
|
||||
@@ -0,0 +1,204 @@
|
||||
-ifndef(__MS_CACHE_HRL__).
|
||||
-define(__MS_CACHE_HRL__, true).
|
||||
|
||||
-include("base.hrl").
|
||||
|
||||
-define(MS_CACHE, ms_cache).
|
||||
|
||||
-define(WORKER_COUNTER_DEFAULT, 8).
|
||||
-define(WORKER_INIT_TIME_MIN, 100).
|
||||
|
||||
%% 需要更新的数据超过指定数量,立即触发存盘,默认只为1000
|
||||
-define(SYNC_NUM, ms_cache_config:get_env(db_save_count, 1000)).
|
||||
|
||||
%% 缓存保存到数据库时间,默认10分钟保存一次
|
||||
-define(DB_SAVE_TIME, ms_cache_config:get_env(db_save_time, 600000)).
|
||||
|
||||
%% 安全模式(待修改的数据有备份到dets或其他落地数据库)
|
||||
-define(SAFE_MODE, ms_cache_config:get_env(safe_mode, false)).
|
||||
|
||||
%% 错误信息
|
||||
-define(ERR_MSG(Tab, Key, Reason), ms_cache_util:get_error_message(Tab, Key, Reason)).
|
||||
|
||||
%% 根据类型获取缓存表名
|
||||
-define(TABLE_NAME(Tab, Type), (ms_cache_util:get_table_name(Tab, Type))).
|
||||
-define(TABLE_NAME(Tab, Type, WorkNum), (ms_cache_util:get_table_name(Tab, Type, WorkNum))).
|
||||
|
||||
%% 获取缓存数据节点
|
||||
-define(DATA_NODE, (ms_cache_util:get_data_node())).
|
||||
|
||||
%% 获取缓存表处理的模块
|
||||
%%-define(TABLE_HANDLER(Tab), (list_to_atom(lists:concat([Tab, "_c"])))).
|
||||
-define(TABLE_HANDLER(Tab), (ms_cache_config:get({?MS_CACHE, handler, Tab}))).
|
||||
-define(SET_TABLE_HANDLER(Tab, Handler), (ms_cache_config:put({?MS_CACHE, handler, Tab}, Handler))).
|
||||
|
||||
%% 缓存数据转换为record形式
|
||||
-define(TO_REC(Data), (ms_cache_util:map_to_record(Data))).
|
||||
|
||||
%% 缓存数据转换为map形式
|
||||
-define(TO_MAP(Data), (ms_cache_util:record_to_map(Data))).
|
||||
|
||||
%% 缓存数据格式转换
|
||||
-define(FMT_DATA(Data), (ms_cache_util:format_data(Data))).
|
||||
|
||||
%% 单次清理缓存数据的数量
|
||||
-define(CACHE_REMOVE_LIMIT, 1000).
|
||||
|
||||
%% 获取数据库连接池名
|
||||
-define(SELECT_POOL, ms_cache_config:get_env(db_select_pool_name, undefined)).
|
||||
-define(UPDATE_POOL, ms_cache_config:get_env(db_update_pool_name, undefined)).
|
||||
|
||||
%% 读写模式 concurrency | serial
|
||||
-define(CONCURRENCY, concurrency).
|
||||
-define(SERIAL, serial).
|
||||
-define(RW_MODE, ms_cache_config:get_env(rw_mode, ?CONCURRENCY)).
|
||||
|
||||
%% 数据同步模式 data | key todo key模式当前有bug,关服时缓存data表先被删除,导致同步进程查询的时候报错
|
||||
-define(DATA_SYNC_MODE_DATA, data). %% 同步变化的整个数据 record | map
|
||||
-define(DATA_SYNC_MODE_KEY, key). %% 只同步变化的key
|
||||
-define(DATA_SYNC_MODE, ms_cache_config:get_env(data_sync_mode, ?DATA_SYNC_MODE_DATA)).
|
||||
|
||||
%% 获取表的handler名称
|
||||
-define(TABLE_HANDLER_NAME(Table), list_to_atom(lists:concat([Table, "_c"]))).
|
||||
|
||||
-define(MS_CACHE_DB_DROP_AUTH, ms_cache_config:get_env(db_drop_auth, ?FALSE)).
|
||||
|
||||
-define(OPT_TABLE_MAX, 10).
|
||||
-define(OPT_TABLE_LIST(Table), [erlang:list_to_atom(erlang:atom_to_list(?TABLE_NAME(Table, opt)) ++ "_" ++ erlang:integer_to_list(Num)) || Num<-lists:seq(1, ?OPT_TABLE_MAX)]).
|
||||
|
||||
|
||||
%% 缓存使用的内存表.
|
||||
%% ms_cache_mdb : mnesia作为缓存表
|
||||
%% ms_cache_edb : ets set数据表 + ets ordered_set索引表 (仿mnesia写法,mnesia简化版,综合性能最好)
|
||||
%% ms_cache_edb2 : ets set数据表 + ets bag索引表 (插入和删除性能较差)
|
||||
%% ms_cache_edb3 : ets set数据表 + ets set索引表 (大数据性能差,别用,只用来测试对比)
|
||||
-define(MS_CACHE_DB_MOD, (ms_cache_util:get_cache_db_mod())).
|
||||
|
||||
%% 数据库操作标志
|
||||
-record(ms_cache_opt, {
|
||||
key = 0,
|
||||
opt = none, %% insert | update | delete
|
||||
opt_rec = none
|
||||
}).
|
||||
|
||||
%% 复合主键的数据,以第一个key作为索引的所有key列表
|
||||
-record(ms_cache_idx, {
|
||||
key = 0,
|
||||
val = []
|
||||
}).
|
||||
|
||||
%% 数据加载标志
|
||||
-record(ms_cache_load, {
|
||||
key = 0,
|
||||
val = none %%{Key, none} - 无Key对应数据 | {Index, index_loaded} - 复合主键以第一个key为所以的数据已加载 | {Table, table_loaded} - 整表数据已加载
|
||||
}).
|
||||
|
||||
-record(ms_cache_expired, {
|
||||
key = 0, %% 缓存清理key | 缓存清理index(复合主键)
|
||||
val = none
|
||||
}).
|
||||
|
||||
|
||||
|
||||
|
||||
%% atom define
|
||||
-ifndef(OK).
|
||||
-define(OK, ok).
|
||||
-endif.
|
||||
-ifndef(TRUE).
|
||||
-define(TRUE, true).
|
||||
-endif.
|
||||
-ifndef(FALSE).
|
||||
-define(FALSE, false).
|
||||
-endif.
|
||||
-ifndef(UNDEFINED).
|
||||
-define(UNDEFINED, undefined).
|
||||
-endif.
|
||||
-ifndef(INDEFINITELY).
|
||||
-define(INDEFINITELY, indefinitely).
|
||||
-endif.
|
||||
-ifndef(CONTINUE).
|
||||
-define(CONTINUE, continue).
|
||||
-endif.
|
||||
-ifndef(IGNORE).
|
||||
-define(IGNORE, ignore).
|
||||
-endif.
|
||||
-ifndef(INFINITY).
|
||||
-define(INFINITY, infinity).
|
||||
-endif.
|
||||
-ifndef(ERROR).
|
||||
-define(ERROR, error).
|
||||
-endif.
|
||||
-ifndef(NONE).
|
||||
-define(NONE, none).
|
||||
-endif.
|
||||
-ifndef(STOP).
|
||||
-define(STOP, stop).
|
||||
-endif.
|
||||
-ifndef(SKIP).
|
||||
-define(SKIP, skip).
|
||||
-endif.
|
||||
|
||||
%% 封装处理handle_continue
|
||||
-define(TRY_CONTINUE(Msg, State),
|
||||
try
|
||||
do_continue(Msg, State)
|
||||
catch _:ErrMsgDoNotRepeatName:Trace ->
|
||||
?ERROR(?MODULE, "handle continue: Msg = ~p, ErrMsg = ~p, Trace = ~p", [Msg, ErrMsgDoNotRepeatName, Trace]),
|
||||
{noreply, State}
|
||||
end
|
||||
).
|
||||
%% 封装处理handle_call
|
||||
-define(TRY_CALL(Msg, From, State),
|
||||
try
|
||||
do_call(Msg, From, State)
|
||||
catch _:ErrMsgDoNotRepeatName:Trace ->
|
||||
?ERROR(?MODULE, "handle call: Msg = ~p, ErrMsg = ~p, Trace = ~p", [Msg, ErrMsgDoNotRepeatName, Trace]),
|
||||
{reply, ok, State}
|
||||
end
|
||||
).
|
||||
-define(TRY_CALL(Msg, State),
|
||||
try
|
||||
do_call(Msg, State)
|
||||
catch _:ErrMsgDoNotRepeatName:Trace ->
|
||||
?ERROR(?MODULE, "handle call: Msg = ~p, ErrMsg = ~p, Trace = ~p", [Msg, ErrMsgDoNotRepeatName, Trace]),
|
||||
{reply, ok, State}
|
||||
end
|
||||
).
|
||||
%% 封装处理handle_cast
|
||||
-define(TRY_CAST(Msg, State),
|
||||
try
|
||||
do_cast(Msg, State)
|
||||
catch _:ErrMsgDoNotRepeatName:Trace ->
|
||||
?ERROR(?MODULE, "handle cast: Msg = ~p, ErrMsg = ~p, Trace = ~p", [Msg, ErrMsgDoNotRepeatName, Trace]),
|
||||
{noreply, State}
|
||||
end
|
||||
).
|
||||
%% 封装处理handle_info
|
||||
-define(TRY_INFO(Msg, State),
|
||||
try
|
||||
do_info(Msg, State)
|
||||
catch _:ErrMsgDoNotRepeatName:Trace ->
|
||||
?ERROR(?MODULE, "handle info: Msg = ~p, ErrMsg = ~p, Trace = ~p", [Msg, ErrMsgDoNotRepeatName, Trace]),
|
||||
{noreply, State}
|
||||
end
|
||||
).
|
||||
|
||||
|
||||
%%% 打印函数
|
||||
|
||||
%% 进程字典的读取和存储
|
||||
-define(PUT_TO_PROC_DICT(Key, Val), erlang:put({?MODULE, Key}, Val)).
|
||||
-define(GET_FROM_PROC_DICT(Key), erlang:get({?MODULE, Key})).
|
||||
-define(GET_FROM_PROC_DICT(Key, Default),
|
||||
case erlang:get({?MODULE, Key}) of
|
||||
?UNDEFINED ->
|
||||
Default;
|
||||
Val ->
|
||||
Val
|
||||
end ).
|
||||
-define(ERASE_FROM_PROC_DICT(Key), erlang:erase({?MODULE, Key})).
|
||||
|
||||
-type ms_cache_key() :: integer() | atom() | binary().
|
||||
|
||||
-endif.
|
||||
@@ -0,0 +1,17 @@
|
||||
{application, ms_cache,
|
||||
[{description, "An OTP application"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{mod, {ms_cache_app, []}},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
mysql_poolboy,
|
||||
mnesia
|
||||
]},
|
||||
{env,[]},
|
||||
{modules, []},
|
||||
|
||||
{licenses, ["Apache 2.0"]},
|
||||
{links, []}
|
||||
]}.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
%%% -------------------------------------------------------------------
|
||||
%%% Author : wangyalun
|
||||
%%% Description : 缓存application
|
||||
%%% Created : 2019-01-14
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(ms_cache_app).
|
||||
|
||||
-include("base.hrl").
|
||||
-behaviour(application).
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
%% Application callbacks
|
||||
-export([start/2, stop/1]).
|
||||
-export([start_all/0]).
|
||||
|
||||
%% ===================================================================
|
||||
%% Application callbacks
|
||||
%% ===================================================================
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
case ms_cache_sup:start_link() of
|
||||
{ok, Sup} ->
|
||||
case ms_cache_util:get_cache_db_mod() of
|
||||
ms_cache_mdb ->
|
||||
case mnesia:system_info(use_dir) of
|
||||
true ->
|
||||
mnesia:start();
|
||||
false ->
|
||||
mnesia:stop(),
|
||||
mnesia:create_schema([erlang:node()]),
|
||||
mnesia:start()
|
||||
end,
|
||||
%% 这句语句,会使所有mnesia的ram_copies表结构不存储在本地磁盘;不加的话上次关服创建的表下次启服时表结构是存在的
|
||||
mnesia:change_table_copy_type(schema, node(), ram_copies),
|
||||
|
||||
%% 同步表使用dets存储,不用mnesia的愿意如下
|
||||
%% 其他表(数据表、加载表和过期表使用mnesia临时表)
|
||||
%% 这里的原因是mnesia:change_table_copy_type(schema, node(), ram_copies)之后建的都是内存临时表
|
||||
%% 而同步表,在数据库连不上 /节点挂掉的情况,需要存储,减少数据的丢失,所以不能用mnesia临时表
|
||||
%% 如果mnesia不指定为临时表,则其他表的表结构会存在mnesia,如果结构变动启动服务器非常慢
|
||||
?IF(?SAFE_MODE, filelib:ensure_dir("dets/*"));
|
||||
_ ->
|
||||
?SKIP
|
||||
end,
|
||||
{ok, Sup};
|
||||
Error ->
|
||||
?ERROR(?MODULE, "application start error : ~p", [Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
stop(_State) ->
|
||||
?INFO(?MODULE, "stop ms_cache application", []),
|
||||
ok.
|
||||
|
||||
%% rebar3热更会用版本内的sys.config的配置替换运行的代码,导致热更的application的envs被替换
|
||||
%% 游戏各个服配置不一样,难以在打升级包的时候就把sys.config定义好
|
||||
%% 通常情况热更的时候,这些配置就会被替换成空失效
|
||||
%% config_change是热更时替换sys.config数据后调用被热更配置的app,所以在这里将app的ets回写到ac_tab
|
||||
%% 注意:不能用application:set_env来处理,call会导致死锁,因为这个处理是在application_controller进程执行
|
||||
%%config_change(_Changed, _New, _Removed) ->
|
||||
%% %% 统一由启动cache的app去修改app的环境变量
|
||||
%% ?INFO(?MODULE, "ms_cache hotfix config change", []),
|
||||
%% ok.
|
||||
|
||||
start_all() ->
|
||||
application:start(sasl),
|
||||
application:start(crypto),
|
||||
application:start(inets),
|
||||
application:start(asn1),
|
||||
application:start(public_key),
|
||||
application:start(ssl),
|
||||
application:start(goldrush),
|
||||
application:start(mysql_poolboy),
|
||||
application:start(ms_cache),
|
||||
ok.
|
||||
@@ -0,0 +1,50 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 10. 1月 2025 11:39
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_config).
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% API
|
||||
-export([
|
||||
get_env/2
|
||||
, put/2
|
||||
, get/1
|
||||
, get/2
|
||||
]).
|
||||
|
||||
%% 获取application的环境变量,application:get_env/2
|
||||
get_env(Key, Def) ->
|
||||
case persistent_term:get({?MODULE, env, Key}, {?UNDEFINED, ?NONE}) of
|
||||
{?UNDEFINED, ?NONE} ->
|
||||
case application:get_env(?MS_CACHE, Key) of
|
||||
?UNDEFINED ->
|
||||
Def;
|
||||
{?OK, Value} ->
|
||||
persistent_term:put({?MODULE, env, Key}, Value),
|
||||
Value
|
||||
end;
|
||||
Value ->
|
||||
Value
|
||||
end.
|
||||
%% application:get_env(?MS_CACHE, Key, Def).
|
||||
|
||||
%% 设置常量
|
||||
put(Key, Value) ->
|
||||
case persistent_term:get({?MODULE, const, Key}, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
persistent_term:put({?MODULE, const, Key}, Value),
|
||||
?OK;
|
||||
Value ->
|
||||
error({error_key_exist, Key, Value})
|
||||
end.
|
||||
|
||||
%% 获取设置的常量
|
||||
get(Key) ->
|
||||
get(Key, ?UNDEFINED).
|
||||
|
||||
get(Key, Def) ->
|
||||
persistent_term:get({?MODULE, const, Key}, Def).
|
||||
@@ -0,0 +1,395 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, <COMPANY>
|
||||
%%% @doc
|
||||
%%% ms_cache与数据库交互的进程
|
||||
%%% @end
|
||||
%%% Created : 2019-09-01 00:00:00
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_dao_merge_gs).
|
||||
-include("base.hrl").
|
||||
-behaviour(gen_server).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% include
|
||||
%%--------------------------------------------------------------------
|
||||
-include("ms_cache.hrl").
|
||||
%% Auto recovered record definitions extracted from decompiled Erlang source.
|
||||
%% Keep record definitions in headers; do not define them directly in .erl files.
|
||||
|
||||
-record(state, {}).
|
||||
%%--------------------------------------------------------------------
|
||||
%% API exports
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-export([
|
||||
start_link/2
|
||||
, set_opt/5 %% 设置数据操作标志
|
||||
, hand_sync/1 %% 手动同步数据到数据库
|
||||
, hand_sync/2 %% 手动同步指定数据到数据库
|
||||
, set_remove_cache/2 %% 设置移除缓存
|
||||
, del_remove_cache/2 %% 取消移除缓存
|
||||
, sync_finish/2 %% dao进程通知同步完毕
|
||||
%% , create_table/2
|
||||
]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
-export([
|
||||
init/1
|
||||
,handle_continue/2
|
||||
,handle_call/3
|
||||
,handle_cast/2
|
||||
,handle_info/2
|
||||
,terminate/2
|
||||
,code_change/3
|
||||
]).
|
||||
|
||||
%% 缓存数据表与数据库交互的进程
|
||||
-define(SERVER(Table), list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Table))).
|
||||
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Starts the server
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(start_link(Table :: atom(), SafeMode :: boolean()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Table, SafeMode) ->
|
||||
gen_server:start_link({local, ?SERVER(Table)}, ?MODULE, [Table, SafeMode], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
%% 设置数据操作标志
|
||||
set_opt(Table, Key, Otp, Record, Flush) ->
|
||||
SyncData = ?IF(?DATA_SYNC_MODE == ?DATA_SYNC_MODE_DATA, Record, ?NONE),
|
||||
gen_server:cast(?SERVER(Table), {set_opt, Key, Otp, SyncData, Flush}).
|
||||
|
||||
hand_sync(Table) ->
|
||||
erlang:send(?SERVER(Table), hand_sync, [noconnect]).
|
||||
|
||||
hand_sync(Table, KeyOrKeys) ->
|
||||
erlang:send(?SERVER(Table), {hand_sync, KeyOrKeys}, [noconnect]).
|
||||
|
||||
set_remove_cache(Table, Index) ->
|
||||
gen_server:cast(?SERVER(Table), {set_remove_cache, Index, erlang:self()}).
|
||||
|
||||
del_remove_cache(Table, Index) ->
|
||||
gen_server:cast(?SERVER(Table), {del_remove_cache, Index, erlang:self()}).
|
||||
|
||||
sync_finish(Table, OptTable) ->
|
||||
gen_server:cast(?SERVER(Table), {sync_finish, OptTable}).
|
||||
|
||||
%%create_table(Table, SafeMode) ->
|
||||
%% {?OK, State} = get_default_state(Table, SafeMode),
|
||||
%%%% ms_cache_dao_merge_gs_lib:create_table(State),
|
||||
%% ?OK.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Initializes the server
|
||||
%%
|
||||
%% @spec init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(init(Args :: term()) ->
|
||||
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term()} | ignore).
|
||||
|
||||
init([Table, SafeMode]) ->
|
||||
%% 首次存档时间
|
||||
FirstTime = ?WORKER_INIT_TIME_MIN + rand:uniform(?DB_SAVE_TIME),
|
||||
%% ?DEBUG(?MODULE, "~p init", [?SERVER(Table)]),
|
||||
erlang:process_flag(trap_exit, true),
|
||||
{?OK, State} = get_default_state(Table, SafeMode),
|
||||
DbSaveTime = maps:get(db_save_time, State),
|
||||
TimerRef = erlang:start_timer(DbSaveTime + FirstTime, self(), sync),
|
||||
{ok, State#{timer_ref:=TimerRef}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling all non call/cast messages
|
||||
%%
|
||||
%% @spec handle_continue(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_continue(Info :: timeout() | term(), State :: #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_continue(Info, State) ->
|
||||
?TRY_CONTINUE(Info, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling call messages
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
|
||||
State :: #state{}) ->
|
||||
{reply, Reply :: term(), NewState :: #state{}} |
|
||||
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_call(Request, From, State) ->
|
||||
?TRY_CALL(Request, From, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling cast messages
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_cast(Request :: term(), State :: #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_cast(Request, State) ->
|
||||
?TRY_CAST(Request, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling all non call/cast messages
|
||||
%%
|
||||
%% @spec handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_info(Info, State) ->
|
||||
?TRY_INFO(Info, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any
|
||||
%% necessary cleaning up. When it returns, the gen_server terminates
|
||||
%% with Reason. The return value is ignored.
|
||||
%%
|
||||
%% @spec terminate(Reason, State) -> void()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(Reason, #{ tab:=Table } = State) ->
|
||||
?DEBUG(?MODULE, "~p terminate, Reason = ~p, Table = ~p", [?SERVER(Table), Reason, Table]),
|
||||
ms_cache_dao_merge_gs_lib:do_sync(State, terminate),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Convert process state when code is changed
|
||||
%%
|
||||
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
|
||||
Extra :: term()) ->
|
||||
{ok, NewState :: #state{}} | {error, Reason :: term()}).
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks Internal
|
||||
%%%===================================================================
|
||||
do_continue(check_limit_sync, #{opt_tabs:=[], timer_ref:=?UNDEFINED} = State) ->
|
||||
{noreply, State};
|
||||
do_continue(check_limit_sync, #{opt_tabs:=[], timer_ref:=OldTimerRef} = State) ->
|
||||
NewState =
|
||||
case ms_cache_dao_merge_gs_lib:is_sync_now(State) of
|
||||
?TRUE ->
|
||||
erlang:erase(OldTimerRef),
|
||||
State#{timer_ref:=?UNDEFINED};
|
||||
?FALSE ->
|
||||
State
|
||||
end,
|
||||
{noreply, NewState};
|
||||
do_continue(check_limit_sync, #{opt_tabs:=[H | T], timer_ref := OldTimerRef, db_save_time := DbSaveTime} = State) ->
|
||||
NewState =
|
||||
case ms_cache_dao_merge_gs_lib:is_sync_now(State) of
|
||||
?TRUE ->
|
||||
erlang:erase(OldTimerRef),
|
||||
TimerRef = erlang:start_timer(DbSaveTime, self(), sync),
|
||||
ms_cache_dao_merge_gs_lib:do_sync(State, limit_sync),
|
||||
%% ?INFO(?MODULE, "change OptTable, ~p => ~p", [maps:get(opt_tab, State), H]),
|
||||
State#{ opt_tab:=H, opt_tabs:=T, timer_ref:=TimerRef };
|
||||
?FALSE ->
|
||||
State
|
||||
end,
|
||||
{noreply, NewState}.
|
||||
|
||||
do_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
do_cast({set_opt, _, delete_all_objects, _, _}, #{ delete_all_objects:=?FALSE } = State) ->
|
||||
%% 删除所有需要待修改的数据
|
||||
ms_cache_dao_merge_gs_lib:delete_all_objects(State),
|
||||
{noreply, State#{ delete_all_objects:=?TRUE }};
|
||||
do_cast({set_opt, _, delete_all_objects, _, _}, #{ delete_all_objects:=?TRUE } = State) -> %% 数据已被删除
|
||||
{noreply, State};
|
||||
|
||||
do_cast({set_opt, Key, insert, Record, Flush}, State) ->
|
||||
case ms_cache_dao_merge_gs_lib:lookup(State, Key) of
|
||||
[] ->
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=insert,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=delete }] -> %% 如果还没有被删除,变为更新
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=update }] -> %% 有可能truncate表后(truncate临界区update),重新插入
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=insert,opt_rec = OldRecord }] -> %% 重复插入以最新的数据为准(兼容写法)
|
||||
?ERROR(?MODULE, "inserted when set opt insert, OldRec = ~p, NewRec = ~p", [OldRecord, Record]),
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=insert,opt_rec=Record });
|
||||
Other -> %% 未知情况,修改为插入(兼容写法)
|
||||
?ERROR(?MODULE, "other opt seted when set opt insert, Other = ~p, NewRec = ~p", [Other, Record]),
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=insert,opt_rec=Record })
|
||||
end,
|
||||
?IF(Flush, ms_cache_dao_merge_gs_lib:try_sync_one_to_db(State, Key)),
|
||||
{noreply, State#{ delete_all_objects:=?FALSE }, {continue, check_limit_sync}}; %% 设置数据未被全部删除
|
||||
do_cast({set_opt, Key, update, Record, Flush}, State) ->
|
||||
case ms_cache_dao_merge_gs_lib:lookup(State, Key) of
|
||||
[] ->
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=insert }] ->
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=insert,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=update }] ->
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record });
|
||||
[#ms_cache_opt{ opt=delete }] -> %% 这种情况应该不存在(兼容写法)
|
||||
?ERROR(?MODULE, "deleted when set opt update, Record = ~p", [Record]),
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record });
|
||||
Other -> %% 未知情况,修改为更新(兼容写法)
|
||||
?ERROR(?MODULE, "other opt seted when set opt update, Other = ~p, NewRec = ~p", [Other, Record]),
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=update,opt_rec=Record })
|
||||
end,
|
||||
?IF(Flush, ms_cache_dao_merge_gs_lib:try_sync_one_to_db(State, Key)),
|
||||
{noreply, State#{ delete_all_objects:=?FALSE }, {continue, check_limit_sync}}; %% 设置数据未被全部删除
|
||||
do_cast({set_opt, Key, delete, _, Flush}, State) ->
|
||||
case ms_cache_dao_merge_gs_lib:lookup(State, Key) of
|
||||
[] -> %% 还没插入直接删掉插入标志即可
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=delete,opt_rec=none });
|
||||
[#ms_cache_opt{ opt=insert }] -> %% 还没插入直接删掉插入标志即可
|
||||
ms_cache_dao_merge_gs_lib:delete(State, Key);
|
||||
[#ms_cache_opt{ opt=update }] -> %% 还没插入直接删掉插入标志即可
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=delete,opt_rec=none });
|
||||
[#ms_cache_opt{ opt=delete }] -> %% 重复删除,兼容写法
|
||||
%% ?ERROR(?MODULE, "deleted when set opt delete (duplicate delete), OptTable~p Key = ~p", [OptTable, Key]),
|
||||
ok;
|
||||
Other -> %% 未知情况,修改为删除(兼容写法)
|
||||
?ERROR(?MODULE, "other opt seted when set opt delete, Other = ~p, Key = ~p", [Other, Key]),
|
||||
ms_cache_dao_merge_gs_lib:insert(State, #ms_cache_opt{ key=Key,opt=delete,opt_rec=none })
|
||||
end,
|
||||
?IF(Flush, ms_cache_dao_merge_gs_lib:try_sync_one_to_db(State, Key)),
|
||||
{noreply, State, {continue, check_limit_sync}};
|
||||
do_cast({set_remove_cache, Index, Pid}, #{opt_tab:=OptTable} = State) ->
|
||||
ms_cache_dao_merge_gs_lib:set_remove_cache(OptTable, Index, Pid),
|
||||
{noreply, State};
|
||||
do_cast({del_remove_cache, Index, _Pid}, #{opt_tab:=OptTable} = State) ->
|
||||
ms_cache_dao_merge_gs_lib:del_remove_cache(OptTable, Index),
|
||||
{noreply, State};
|
||||
%% 手动同步
|
||||
%% TODO 同步在同一个进程数据量大可能会堵消息,先阻塞慢慢消耗消息吧,毕竟这个进程并不影响业务(cup/内存不爆的情况)
|
||||
%%do_cast(hand_sync, State) ->
|
||||
%% ms_cache_dao_merge_gs_lib:do_sync(State, hand_sync),
|
||||
%% {noreply, State};
|
||||
do_cast(hand_sync, #{tab:=Table, opt_tab:=OptTable, opt_tabs := [H | T], timer_ref := OldTimerRef, db_save_time := DbSaveTime} = State) ->
|
||||
erlang:erase(OldTimerRef),
|
||||
TimerRef = erlang:start_timer(DbSaveTime, self(), sync),
|
||||
ms_cache_dao_sync_gs:sync(Table, OptTable, timeout_sync),
|
||||
%% ?INFO(?MODULE, "change OptTable, ~p => ~p", [maps:get(opt_tab, State), H]),
|
||||
{noreply, State#{ opt_tab=>H, opt_tabs=>T, timer_ref=>TimerRef }};
|
||||
do_cast(hand_sync, #{tab:=Table, opt_tabs := [], timer_ref := OldTimerRef} = State) ->
|
||||
?IF(erlang:is_reference(OldTimerRef), erlang:erase(OldTimerRef)),
|
||||
?ERROR(?MODULE, "no new opt_table to use, Table = ~p", [Table]),
|
||||
{noreply, State#{ timer_ref:=?UNDEFINED }};
|
||||
%% 手动同步同步多个数据到数据库
|
||||
do_cast({hand_sync, Keys}, State) when is_list(Keys) ->
|
||||
ms_cache_dao_merge_gs_lib:try_sync_list_to_db(State, Keys),
|
||||
{noreply, State};
|
||||
%% 手动同步同步单一数据到数据库
|
||||
do_cast({hand_sync, Key}, State) ->
|
||||
ms_cache_dao_merge_gs_lib:try_sync_one_to_db(State, Key),
|
||||
{noreply, State};
|
||||
%% 同步完成
|
||||
do_cast({sync_finish, FinishOptTable}, #{tab:=Table, opt_tab := OptTable, opt_tabs := [], timer_ref:=?UNDEFINED, db_save_time := DbSaveTime} = State) ->
|
||||
TimerRef = erlang:start_timer(DbSaveTime, self(), sync),
|
||||
ms_cache_dao_merge_gs_lib:sync_finish(FinishOptTable),
|
||||
ms_cache_dao_sync_gs:sync(Table, OptTable, timeout_sync),
|
||||
{noreply, State#{opt_tab:=FinishOptTable, timer_ref => TimerRef}};
|
||||
do_cast({sync_finish, FinishOptTable}, #{ opt_tabs := OptTables } = State) ->
|
||||
ms_cache_dao_merge_gs_lib:sync_finish(FinishOptTable),
|
||||
{noreply, State#{opt_tabs:=[FinishOptTable | OptTables]}}.
|
||||
|
||||
%% 自动同步
|
||||
do_info({timeout, OldTimerRef, sync}, #{tab:=Table, opt_tab:=OptTable, opt_tabs := [H | T], timer_ref:=OldTimerRef, safe_mode:=SafeMode, db_save_time:=DbSaveTime} = State) ->
|
||||
%% todo 注意这里不能判断OptTable表为空,就走同步数据;这样会导致移除缓存的数据异常
|
||||
TimerRef = erlang:start_timer(DbSaveTime, self(), sync),
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
case DbCacheMod:table_info(OptTable, size) > 0 of
|
||||
?TRUE ->
|
||||
ms_cache_dao_sync_gs:sync(Table, OptTable, timeout_sync),
|
||||
{noreply, State#{ opt_tab=>H, opt_tabs=>T, timer_ref=>TimerRef }};
|
||||
?FALSE ->
|
||||
{noreply, State#{ timer_ref=>TimerRef }}
|
||||
end;
|
||||
do_info({timeout, OldTimerRef, sync}, #{tab:=Table, opt_tabs := [], timer_ref:=OldTimerRef} = State) ->
|
||||
?ERROR(?MODULE, "no new opt_table to use, Table = ~p", [Table]),
|
||||
{noreply, State#{timer_ref=>?UNDEFINED}};
|
||||
do_info({timeout, _TimerRef, sync}, State) ->
|
||||
{noreply, State}.
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
get_default_state(Table, SafeMode) ->
|
||||
[H | T] = ?OPT_TABLE_LIST(Table),
|
||||
Handler = ?TABLE_HANDLER(Table),
|
||||
DbSaveTime =
|
||||
case Handler:db_save_time() of
|
||||
?UNDEFINED ->
|
||||
?DB_SAVE_TIME;
|
||||
DbSaveTime0 ->
|
||||
DbSaveTime0
|
||||
end,
|
||||
DbSaveCount =
|
||||
case Handler:db_save_count() of
|
||||
?UNDEFINED ->
|
||||
?SYNC_NUM;
|
||||
DbSaveCount0 ->
|
||||
DbSaveCount0
|
||||
end,
|
||||
State = #{
|
||||
tab => Table,
|
||||
opt_tab => H,
|
||||
opt_tabs => T,
|
||||
handler => ?TABLE_HANDLER(Table),
|
||||
delete_all_objects => ?FALSE,
|
||||
safe_mode => SafeMode,
|
||||
timer_ref => ?UNDEFINED,
|
||||
db_save_time => DbSaveTime,
|
||||
db_save_count => DbSaveCount
|
||||
},
|
||||
{?OK, State}.
|
||||
@@ -0,0 +1,159 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 03. 7月 2023 20:18
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_dao_merge_gs_lib).
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% API
|
||||
-export([
|
||||
%% create_table/1
|
||||
delete_all_objects/1
|
||||
, lookup/2
|
||||
, insert/2
|
||||
, delete/2
|
||||
, close/1
|
||||
]).
|
||||
|
||||
-export([
|
||||
do_sync/2
|
||||
, is_sync_now/1
|
||||
, get_sync_num/1
|
||||
, try_sync_one_to_db/2
|
||||
, try_sync_list_to_db/2
|
||||
, set_remove_cache/3
|
||||
, del_remove_cache/2
|
||||
, sync_finish/1
|
||||
, sync_finish/2
|
||||
]).
|
||||
|
||||
-define(MS_TKV_PROC_REMOVE_CACHE(OptTable), {remove_cache, OptTable}).
|
||||
-define(REMOVE_CACHE_DICT_KEY(Index), {remove_cache, Index}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
%% @doc
|
||||
%% 删除表所有数据
|
||||
%% @end
|
||||
delete_all_objects(#{ tab:=Table,opt_tab:=OptTable,handler:=Handler,safe_mode:=SafeMode }) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:delete_all_objects(OptTable),
|
||||
%% 删除数据库所有数据
|
||||
Handler:delete_all(?UPDATE_POOL),
|
||||
ms_cache_dao_sync_gs:delete_all_objects(Table),
|
||||
?OK.
|
||||
|
||||
%% @doc
|
||||
%% 查询待同步的数据
|
||||
%% @end
|
||||
lookup(#{ opt_tab:=OptTable, safe_mode:=SafeMode }, Key) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:lookup(OptTable,Key).
|
||||
|
||||
%% @doc
|
||||
%% 插入待同步的数据
|
||||
%% @end
|
||||
insert(#{ opt_tab:=OptTable, safe_mode:=SafeMode }, Record) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:insert(OptTable,Record),
|
||||
?OK.
|
||||
|
||||
%% @doc
|
||||
%% 删除待同步的数据
|
||||
%% @end
|
||||
delete(#{ opt_tab:=OptTable,safe_mode:=SafeMode }, Key) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:delete(OptTable,Key),
|
||||
?OK.
|
||||
|
||||
%% @doc
|
||||
%% 关闭/删除待同步的数据表
|
||||
%% @end
|
||||
close(#{ opt_tab:=OptTable,safe_mode:=SafeMode }) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:close(OptTable),
|
||||
?OK.
|
||||
|
||||
%% @doc
|
||||
%% 检测待同步数据量是否到设定阈值,是则同步,不是则不处理
|
||||
%% @end
|
||||
get_sync_num(#{ opt_tab:=OptTable,safe_mode:=SafeMode }) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
DbCacheMod:table_info(OptTable, size).
|
||||
|
||||
%% @doc
|
||||
%% 检测待同步数据量是否到设定阈值,是则同步,不是则不处理
|
||||
%% @end
|
||||
is_sync_now(#{ opt_tab:=OptTable,safe_mode:=SafeMode,db_save_count:=DbSaveCount }) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
SyncNum = DbCacheMod:table_info(OptTable, size),
|
||||
SyncNum >= DbSaveCount.
|
||||
|
||||
%% @doc
|
||||
%% 指定单笔数据同步到数据库
|
||||
%% @end
|
||||
try_sync_one_to_db(#{ tab:=Table,opt_tab:=OptTable,safe_mode:=SafeMode }, OptKey) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
case DbCacheMod:lookup(OptTable, OptKey) of
|
||||
[] ->
|
||||
ok;
|
||||
[OptRecord] ->
|
||||
ms_cache_dao_sync_gs:sync_one_to_db(Table, OptRecord)
|
||||
%% todo,这里不删除记录,确保数据不丢失;注意这种情况数据库插入要兼容存在时更新(目前是支持的)
|
||||
%% DbCacheMod:delete(OptTable, OptKey)
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% @doc
|
||||
%% 指定单笔数据同步到数据库 todo 暂时没有这个方法的应用场景,先改成多次单笔数据同步到数据库
|
||||
%% @end
|
||||
try_sync_list_to_db(State, OptKeys) ->
|
||||
lists:foreach(fun(OptKey) -> try_sync_one_to_db(State, OptKey) end, OptKeys),
|
||||
ok.
|
||||
|
||||
|
||||
%% @doc
|
||||
%% 所有待同步数据同步到数据库
|
||||
%% @end
|
||||
do_sync(#{ tab:=Table,opt_tab:=OptTable }, SyncType) ->
|
||||
ms_cache_dao_sync_gs:sync(Table, OptTable, SyncType),
|
||||
ok.
|
||||
|
||||
%% @doc
|
||||
%% 获取侦听数据同步事件的进程
|
||||
%% @end
|
||||
sync_finish(OptTable) ->
|
||||
ms_cache_tkv:foreach(
|
||||
?MS_TKV_PROC_REMOVE_CACHE(OptTable),
|
||||
fun(Index, Pid) ->
|
||||
?ERASE_FROM_PROC_DICT(?REMOVE_CACHE_DICT_KEY(Index)),
|
||||
sync_finish(Pid, Index)
|
||||
end
|
||||
),
|
||||
ms_cache_tkv:delete(?MS_TKV_PROC_REMOVE_CACHE(OptTable)),
|
||||
?OK.
|
||||
|
||||
sync_finish(Pid, Index) ->
|
||||
erlang:send(Pid, {sync_finish, Index}, [noconnect]),
|
||||
?OK.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
set_remove_cache(OptTable, Index, Pid) ->
|
||||
?PUT_TO_PROC_DICT(?REMOVE_CACHE_DICT_KEY(Index), OptTable),
|
||||
ms_cache_tkv:add(?MS_TKV_PROC_REMOVE_CACHE(OptTable), Index, Pid),
|
||||
?OK.
|
||||
|
||||
del_remove_cache(_OptTable, Index) ->
|
||||
case ?ERASE_FROM_PROC_DICT(?REMOVE_CACHE_DICT_KEY(Index)) of
|
||||
?UNDEFINED ->
|
||||
?SKIP;
|
||||
OldOptTable ->
|
||||
ms_cache_tkv:delete(?MS_TKV_PROC_REMOVE_CACHE(OldOptTable), Index)
|
||||
end,
|
||||
?OK.
|
||||
@@ -0,0 +1,32 @@
|
||||
-module(ms_cache_dao_merge_gs_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
|
||||
init([]) ->
|
||||
{
|
||||
ok,
|
||||
{
|
||||
{simple_one_for_one, 5, 10},
|
||||
[?CHILD(ms_cache_dao_merge_gs, worker)]
|
||||
}
|
||||
}.
|
||||
@@ -0,0 +1,212 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, <COMPANY>
|
||||
%%% @doc
|
||||
%%% ms_cache与数据库交互的进程
|
||||
%%% @end
|
||||
%%% Created : 2019-09-01 00:00:00
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_dao_sync_gs).
|
||||
-include("base.hrl").
|
||||
-behaviour(gen_server).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% include
|
||||
%%--------------------------------------------------------------------
|
||||
-include("ms_cache.hrl").
|
||||
%% Auto recovered record definitions extracted from decompiled Erlang source.
|
||||
%% Keep record definitions in headers; do not define them directly in .erl files.
|
||||
|
||||
-record(state, {}).
|
||||
%%--------------------------------------------------------------------
|
||||
%% API exports
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-export([
|
||||
start_link/4
|
||||
, set_batch_len/2 %% 设置批量同步数据sql语句的最大长度
|
||||
, sync/3
|
||||
, sync_one_to_db/2
|
||||
, delete_all_objects/1
|
||||
]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
-export([
|
||||
init/1
|
||||
,handle_call/3
|
||||
,handle_cast/2
|
||||
,handle_info/2
|
||||
,terminate/2
|
||||
,code_change/3
|
||||
]).
|
||||
|
||||
%% 缓存数据表与数据库交互的进程
|
||||
-define(SERVER(Table), list_to_atom(atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Table))).
|
||||
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% Starts the server
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(start_link(Table :: atom(), BatchLen :: integer(), RangeMemSize:: integer(), SafeMode :: boolean()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Table, BatchLen, RangeMemSize, SafeMode) ->
|
||||
gen_server:start_link({local, ?SERVER(Table)}, ?MODULE, [Table, BatchLen, RangeMemSize, SafeMode], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
set_batch_len(Table, BatchLen) ->
|
||||
gen_server:cast(?SERVER(Table), {set_batch_len, BatchLen}).
|
||||
|
||||
%% 保存到数据库
|
||||
sync(Table, OptTable, SyncType) ->
|
||||
gen_server:cast(?SERVER(Table), {sync, OptTable, SyncType}).
|
||||
|
||||
%% 保存单个数据到数据库
|
||||
sync_one_to_db(Table, OptRecord) ->
|
||||
gen_server:cast(?SERVER(Table), {sync_one_to_db, OptRecord}).
|
||||
|
||||
delete_all_objects(Table) ->
|
||||
gen_server:cast(?SERVER(Table), {delete_all_objects}).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Initializes the server
|
||||
%%
|
||||
%% @spec init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(init(Args :: term()) ->
|
||||
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term()} | ignore).
|
||||
|
||||
init([Table, BatchLen, RangeMemSize, SafeMode]) ->
|
||||
?DEBUG(?MODULE, "~p init", [?SERVER(Table)]),
|
||||
erlang:process_flag(trap_exit, true),
|
||||
State = #{
|
||||
tab => Table,
|
||||
handler => ?TABLE_HANDLER(Table),
|
||||
batch_len => BatchLen,
|
||||
range_mem_size => RangeMemSize,
|
||||
safe_mode => SafeMode
|
||||
},
|
||||
ms_cache_dao_sync_gs_lib:create_table(Table, SafeMode),
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling call messages
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
|
||||
State :: #state{}) ->
|
||||
{reply, Reply :: term(), NewState :: #state{}} |
|
||||
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_call(Request, From, State) ->
|
||||
?TRY_CALL(Request, From, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling cast messages
|
||||
%%
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_cast(Request :: term(), State :: #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_cast(Request, State) ->
|
||||
?TRY_CAST(Request, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Handling all non call/cast messages
|
||||
%%
|
||||
%% @spec handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
|
||||
{noreply, NewState :: #state{}} |
|
||||
{noreply, NewState :: #state{}, timeout() | hibernate} |
|
||||
{stop, Reason :: term(), NewState :: #state{}}).
|
||||
handle_info(Info, State) ->
|
||||
?TRY_INFO(Info, State).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any
|
||||
%% necessary cleaning up. When it returns, the gen_server terminates
|
||||
%% with Reason. The return value is ignored.
|
||||
%%
|
||||
%% @spec terminate(Reason, State) -> void()
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(Reason, #{ tab:=Table } = _State) ->
|
||||
?DEBUG(?MODULE, "~p terminate, Reason = ~p, Table = ~p", [?SERVER(Table), Reason, Table]),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
%% @doc
|
||||
%% Convert process state when code is changed
|
||||
%%
|
||||
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
|
||||
Extra :: term()) ->
|
||||
{ok, NewState :: #state{}} | {error, Reason :: term()}).
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks Internal
|
||||
%%%===================================================================
|
||||
do_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
do_cast({set_batch_len, BatchLen}, State) ->
|
||||
{noreply, State#{batch_len:=BatchLen}};
|
||||
do_cast({sync, OptTable, SyncType}, State) ->
|
||||
ms_cache_dao_sync_gs_lib:do_sync(State, OptTable, SyncType),
|
||||
{noreply, State};
|
||||
do_cast({sync_one_to_db, OptRecord}, State) ->
|
||||
ms_cache_dao_sync_gs_lib:try_sync_one_to_db(State, OptRecord),
|
||||
{noreply, State};
|
||||
do_cast({delete_all_objects}, State) ->
|
||||
ms_cache_dao_sync_gs_lib:delete_all_objects(State),
|
||||
{noreply, State}.
|
||||
|
||||
%% 手动同步同步单一数据到数据库
|
||||
do_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -0,0 +1,314 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 03. 7月 2023 20:18
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_dao_sync_gs_lib).
|
||||
-include("base.hrl").
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% API
|
||||
-export([
|
||||
do_sync/3
|
||||
, try_sync_one_to_db/2
|
||||
, delete_all_objects/1
|
||||
, create_table/2
|
||||
]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc
|
||||
%% 所有待同步数据同步到数据库
|
||||
%% @end
|
||||
do_sync(#{ tab:=Table,handler:=Handler,batch_len:=BatchLen, range_mem_size:=RangeMemberSize, safe_mode:=SafeMode }, OptTable, SyncType) ->
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
SyncNum = DbCacheMod:table_info(OptTable, size),
|
||||
case Handler:sync_mode() of
|
||||
batch ->
|
||||
batch_sync_to_db(Table, OptTable, Handler, BatchLen, RangeMemberSize, SyncType, DbCacheMod);
|
||||
_ ->
|
||||
one_by_one_sync_to_db(Table, OptTable, Handler, SyncType, DbCacheMod)
|
||||
end,
|
||||
|
||||
case DbCacheMod:table_info(OptTable, size) of
|
||||
LeftNum when LeftNum > 0 ->
|
||||
filelib:ensure_dir("log/cache/"),
|
||||
FileName = "log/cache/" ++ erlang:atom_to_list(OptTable) ++ "-" ++ erlang:integer_to_list(ms_cache_util:now_ms()),
|
||||
?ERROR(?MODULE, "sync left, OptTable = ~p, SyncNum = ~p, LeftNum = ~p, FileName = ~p", [OptTable, SyncNum, LeftNum, FileName]),
|
||||
ets:tab2file(OptTable, FileName);
|
||||
_ ->
|
||||
?SKIP
|
||||
end,
|
||||
|
||||
case SyncType /= terminate of
|
||||
?TRUE ->
|
||||
do_sync_finish_notify(Table, OptTable);
|
||||
?FALSE ->
|
||||
?INFO(?MODULE, "terminate, Table = ~p, SyncNum = ~p", [Table, SyncNum])
|
||||
end,
|
||||
?OK.
|
||||
|
||||
%% @doc
|
||||
%% 指定单笔数据同步到数据库
|
||||
%% @end
|
||||
try_sync_one_to_db(#{ tab:=Table,handler:=Handler }, #ms_cache_opt{opt='insert',opt_rec=OptRecord,key=OptKey}) ->
|
||||
try
|
||||
%% Handler:insert(?UPDATE_POOL, OptRecord)
|
||||
case OptRecord of
|
||||
?NONE ->
|
||||
TableData = ms_cache_util:get_table_name_by_key(Table, data, OptKey),
|
||||
case ms_cache_edb:lookup(TableData, OptKey) of
|
||||
[] ->
|
||||
?OK;
|
||||
[OptRecord1] ->
|
||||
Handler:insert(?UPDATE_POOL, OptRecord1)
|
||||
end;
|
||||
_ ->
|
||||
Handler:insert(?UPDATE_POOL, OptRecord)
|
||||
end
|
||||
catch _ErrCode:ErrMsg:Trace ->
|
||||
%% todo 失败了怎么办,重新返还给ms_cache_dao_merge_gs? 好像也不行,比如本次是update,但是ms_cache_dao_merge_gs已经收到了delete消息,这样会有问题
|
||||
?ERROR(?MODULE, "insert error, Table = ~p, OptRecord = ~p, ErrMsg = ~p, Trace = ~p",
|
||||
[Table, OptRecord, ErrMsg, Trace])
|
||||
end,
|
||||
?OK;
|
||||
try_sync_one_to_db(#{ tab:=Table,handler:=Handler }, #ms_cache_opt{opt='update',opt_rec=OptRecord,key=OptKey}) ->
|
||||
try
|
||||
%% Handler:update(?UPDATE_POOL, OptRecord)
|
||||
case OptRecord of
|
||||
?NONE ->
|
||||
TableData = ms_cache_util:get_table_name_by_key(Table, data, OptKey),
|
||||
case ms_cache_edb:lookup(TableData, OptKey) of
|
||||
[] ->
|
||||
?OK;
|
||||
[OptRecord1] ->
|
||||
Handler:update(?UPDATE_POOL, OptRecord1)
|
||||
end;
|
||||
_ ->
|
||||
Handler:update(?UPDATE_POOL, OptRecord)
|
||||
end
|
||||
catch _ErrCode:ErrMsg:Trace ->
|
||||
%% todo 失败了怎么办,重新返还给ms_cache_dao_merge_gs? 好像也不行,比如本次是update,但是ms_cache_dao_merge_gs已经收到了delete消息,这样会有问题
|
||||
?ERROR(?MODULE, "update error, Table = ~p, OptRecord = ~p, ErrMsg = ~p, Trace = ~p",
|
||||
[Table, OptRecord, ErrMsg, Trace])
|
||||
end,
|
||||
?OK;
|
||||
try_sync_one_to_db(#{ tab:=Table,handler:=Handler }, #ms_cache_opt{opt='delete',key=OptKey}) ->
|
||||
try
|
||||
Handler:delete(?UPDATE_POOL, OptKey)
|
||||
catch _ErrCode:ErrMsg:Trace ->
|
||||
%% todo 失败了怎么办,重新返还给ms_cache_dao_merge_gs? 好像也不行,比如本次是update,但是ms_cache_dao_merge_gs已经收到了delete消息,这样会有问题
|
||||
?ERROR(?MODULE, "delete error, Table = ~p, OptKey = ~p, ErrMsg = ~p, Trace = ~p",
|
||||
[Table, OptKey, ErrMsg, Trace])
|
||||
end,
|
||||
?OK.
|
||||
|
||||
delete_all_objects(#{ handler := Handler }) ->
|
||||
%% 删除数据库所有数据
|
||||
Handler:delete_all(?UPDATE_POOL),
|
||||
?OK.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
sync_list_to_db([Record | T], Opt, Table, OptTable, Handler, CacheDbMod) ->
|
||||
try
|
||||
Key =
|
||||
case Opt of
|
||||
'delete' ->
|
||||
Record;
|
||||
_ ->
|
||||
erlang:element(2, Record)
|
||||
end,
|
||||
sync_one_to_db(#ms_cache_opt{key=Key,opt=Opt,opt_rec=Record}, OptTable, Handler, CacheDbMod)
|
||||
catch _ErrCode:ErrMsg:Trace ->
|
||||
?ERROR(?MODULE, "Table = ~p, Record = ~p, ErrMsg = ~p, OptTable : ~p, Trace = ~p",
|
||||
[Table, Record, ErrMsg, OptTable, Trace])
|
||||
end,
|
||||
sync_list_to_db(T, Opt, Table, OptTable, Handler, CacheDbMod);
|
||||
sync_list_to_db([], _Opt, _Table, _OptTable, _Handler, _CacheDbMod) ->
|
||||
ok.
|
||||
|
||||
sync_one_to_db(#ms_cache_opt{key=Key, opt='insert',opt_rec=Record}, OptTable, Handler, CacheDbMod) ->
|
||||
Handler:insert(?UPDATE_POOL, Record),
|
||||
CacheDbMod:delete(OptTable, Key),
|
||||
ok;
|
||||
sync_one_to_db(#ms_cache_opt{key=Key, opt='update',opt_rec=Record}, OptTable, Handler, CacheDbMod) ->
|
||||
Handler:update(?UPDATE_POOL, Record),
|
||||
CacheDbMod:delete(OptTable, Key),
|
||||
ok;
|
||||
sync_one_to_db(#ms_cache_opt{key=Key, opt='delete'}, OptTable, Handler, CacheDbMod) ->
|
||||
Handler:delete(?UPDATE_POOL, Key),
|
||||
CacheDbMod:delete(OptTable, Key),
|
||||
ok.
|
||||
|
||||
%% 逐条同步到数据库
|
||||
one_by_one_sync_to_db(Table, OptTable, Handler, Type, CacheDbMod) ->
|
||||
SyncNum = CacheDbMod:table_info(OptTable, size),
|
||||
case SyncNum > 0 of
|
||||
true ->
|
||||
Fun =
|
||||
fun(OptRecord) ->
|
||||
try
|
||||
sync_one_to_db(OptRecord, OptTable, Handler, CacheDbMod)
|
||||
catch _ErrCode:ErrMsg:Trace ->
|
||||
?ERROR(?MODULE, "Table = ~p, OptRecord = ~p, ErrMsg = ~p, OptTable : ~p, Trace = ~p",
|
||||
[Table, OptRecord, ErrMsg, OptTable, Trace])
|
||||
end
|
||||
end,
|
||||
BeginSyncTime = ms_cache_util:now_ms(),
|
||||
|
||||
CacheDbMod:foreach(OptTable, Fun),
|
||||
|
||||
EndSyncTime = ms_cache_util:now_ms(),
|
||||
case EndSyncTime-BeginSyncTime > 5000 of
|
||||
true ->
|
||||
?INFO(?MODULE, "db_sync_time_info, Type = ~p, Table = ~p, SyncNum = ~p, SyncTime = ~p",
|
||||
[Type, Table, SyncNum, EndSyncTime-BeginSyncTime]);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% 批量同步到数据库
|
||||
batch_sync_to_db(Table, OptTable, Handler, BatchLen, RangeMemberSize, Type, CacheDbMod) ->
|
||||
SyncNum = CacheDbMod:table_info(OptTable, size),
|
||||
case SyncNum > 0 of
|
||||
true ->
|
||||
BeginSyncTime = ms_cache_util:now_ms(),
|
||||
{ok, InsertAcc, UpdateAcc, DeleteAcc} = get_sync_group_datas(Table, OptTable, CacheDbMod),
|
||||
batch_insert(Table, OptTable, InsertAcc, Handler, BatchLen, CacheDbMod),
|
||||
batch_update(Table, OptTable, UpdateAcc, Handler, BatchLen, CacheDbMod),
|
||||
batch_delete(Table, OptTable, DeleteAcc, Handler, RangeMemberSize, CacheDbMod),
|
||||
|
||||
EndSyncTime = ms_cache_util:now_ms(),
|
||||
case EndSyncTime-BeginSyncTime > 5000 of
|
||||
true ->
|
||||
?INFO(?MODULE, "db_sync_time_info, Type = ~p, Table = ~p, OptTable = ~p, SyncNum = ~p, SyncTime = ~p",
|
||||
[Type, Table, OptTable, SyncNum, EndSyncTime-BeginSyncTime]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
batch_insert(Table, OptTable, Records, Handler, BatchLen, CacheDbMod) ->
|
||||
try
|
||||
ok = Handler:batch_insert(?UPDATE_POOL, Records, BatchLen),
|
||||
[CacheDbMod:delete(OptTable, erlang:element(2, Record)) || Record <- Records],
|
||||
ok
|
||||
catch
|
||||
error:{exit, {timeout, _}, _} = ErrMsg -> %% 超时不触发逐笔插入,防止一直超时
|
||||
?ERROR(?MODULE, "batch_insert timeout, Table = ~p, ErrMsg = ~p", [Table, ErrMsg]),
|
||||
ok;
|
||||
_Error:ErrMsg:Trace -> %% 数据库超时可能触发两次操作,理论上也没什么问题
|
||||
?ERROR(?MODULE, "batch_insert error, try sync_list_to_db, Table = ~p, ErrMsg = ~p, stacktrace = ~p",
|
||||
[Table, ErrMsg, Trace]),
|
||||
sync_list_to_db(Records, insert, Table, OptTable, Handler, CacheDbMod),
|
||||
ok
|
||||
end.
|
||||
|
||||
batch_update(Table, OptTable, Records, Handler, BatchLen, CacheDbMod) ->
|
||||
try
|
||||
%% 使用batch_insert來替代batch_update
|
||||
ok = Handler:batch_insert(?UPDATE_POOL, Records, BatchLen),
|
||||
[CacheDbMod:delete(OptTable, erlang:element(2, Record)) || Record <- Records],
|
||||
ok
|
||||
catch
|
||||
error:{exit, {timeout, _}, _} = ErrMsg -> %% 超时不触发逐笔插入,防止一直超时
|
||||
?ERROR(?MODULE, "batch_update timeout, Table = ~p, ErrMsg = ~p", [Table, ErrMsg]),
|
||||
ok;
|
||||
_Error:ErrMsg:Trace -> %% 数据库超时可能触发两次操作,理论上也没什么问题
|
||||
?ERROR(?MODULE, "batch_update error, try sync_list_to_db, Table = ~p, ErrMsg = ~p, stacktrace = ~p",
|
||||
[Table, ErrMsg, Trace]),
|
||||
sync_list_to_db(Records, update, Table, OptTable, Handler, CacheDbMod),
|
||||
ok
|
||||
end.
|
||||
|
||||
batch_delete(Table, OptTable, Keys, Handler, BatchLen, CacheDbMod) ->
|
||||
try
|
||||
ok = Handler:batch_delete(?UPDATE_POOL, Keys, BatchLen),
|
||||
[CacheDbMod:delete(OptTable, Key) || Key <- Keys],
|
||||
ok
|
||||
catch
|
||||
error:{exit, {timeout, _}, _} = ErrMsg -> %% 超时不触发逐笔插入,防止一直超时
|
||||
?ERROR(?MODULE, "batch_delete timeout, Table = ~p, ErrMsg = ~p", [Table, ErrMsg]),
|
||||
ok;
|
||||
_Error:ErrMsg:Trace -> %% 数据库超时可能触发两次操作,理论上也没什么问题
|
||||
?ERROR(?MODULE, "batch_delete error, try sync_list_to_db, Table = ~p, ErrMsg = ~p, stacktrace = ~p",
|
||||
[Table, ErrMsg, Trace]),
|
||||
sync_list_to_db(Keys, delete, Table, OptTable, Handler, CacheDbMod),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% 分组所有插入 更新 删除的记录
|
||||
get_sync_group_datas(Table, OptTable, CacheDbMod) ->
|
||||
Fun =
|
||||
fun(E, {InsertAcc, UpdateAcc, DeleteAcc}) ->
|
||||
case E of
|
||||
#ms_cache_opt{ key=Key,opt='insert',opt_rec = ?NONE } ->
|
||||
DataTable = ms_cache_util:get_table_name_by_key(Table, data, Key),
|
||||
case ms_cache_edb:lookup(DataTable, Key) of
|
||||
[] -> %% 已经被删除,后续会收到delete操作
|
||||
{InsertAcc, UpdateAcc, DeleteAcc};
|
||||
[R] ->
|
||||
{[R | InsertAcc], UpdateAcc, DeleteAcc}
|
||||
end;
|
||||
#ms_cache_opt{ key=Key,opt='update',opt_rec = ?NONE } ->
|
||||
DataTable = ms_cache_util:get_table_name_by_key(Table, data, Key),
|
||||
case ms_cache_edb:lookup(DataTable, Key) of
|
||||
[] -> %% 已经被删除,后续会收到delete操作
|
||||
{InsertAcc, UpdateAcc, DeleteAcc};
|
||||
[R] ->
|
||||
{InsertAcc, [R | UpdateAcc], DeleteAcc}
|
||||
end;
|
||||
#ms_cache_opt{ opt='insert',opt_rec=R } ->
|
||||
{[R | InsertAcc], UpdateAcc, DeleteAcc};
|
||||
#ms_cache_opt{ opt='update',opt_rec=R } ->
|
||||
{InsertAcc, [R | UpdateAcc], DeleteAcc};
|
||||
#ms_cache_opt{ opt='delete',key=Key } ->
|
||||
{InsertAcc, UpdateAcc, [Key | DeleteAcc]}
|
||||
end
|
||||
end,
|
||||
{InsertAcc1, UpdateAcc1, DeleteAcc1} = CacheDbMod:foldl(OptTable, Fun, {[], [] ,[]}),
|
||||
{ok, InsertAcc1, UpdateAcc1, DeleteAcc1}.
|
||||
|
||||
%% 通知侦听finish_notify的进程(即缓存进程ms_cache_gs)同步完成
|
||||
do_sync_finish_notify(Table, OptTable) ->
|
||||
ms_cache_dao_merge_gs:sync_finish(Table, OptTable),
|
||||
ok.
|
||||
|
||||
%% 创建待同步数据表
|
||||
%% @end
|
||||
create_table(Table, SafeMode) ->
|
||||
%% 同步表使用dets存储,不用mnesia的原因如下
|
||||
%% 其他表(数据表、加载表和过期表使用mnesia临时表)
|
||||
%% 启动时调用了mnesia:change_table_copy_type(schema, node(), ram_copies),之后创建的mnesia表都是内存临时表
|
||||
%% 而同步表,在数据库连不上 /节点挂掉的情况,需要存储,减少数据的丢失,所以不能用mnesia临时表
|
||||
%% 如果mnesia不指定为临时表,则其他表的表结构会存在mnesia;如果结构变动要删除旧表再创新表,启动服务器非常慢
|
||||
%% todo 也有可能是对mnesia表类型的操作还不够深入,使用不正确导致,后面看看是否有更合适的方法,暂时先用dets实现
|
||||
%% 这里使用ets是因为ets的性能比mnesia高出一个数量级(5被差距左右)
|
||||
DbCacheMod = ms_cache_util:get_cache_opt_mod(SafeMode),
|
||||
lists:foreach(
|
||||
fun(OT) ->
|
||||
DbCacheMod:create_table(OT, []),
|
||||
Size = DbCacheMod:table_info(OT, size),
|
||||
case Size > 0 of
|
||||
?TRUE -> %% 修复数据(只有SafeMode=true,dets表的模式下才会同步)
|
||||
?INFO(?MODULE, "repair datas, Table = ~p, size = ~p", [Table, Size]),
|
||||
ms_cache_dao_sync_gs:sync(Table, OT, repair_sync);
|
||||
?FALSE ->
|
||||
?SKIP
|
||||
end
|
||||
end, ?OPT_TABLE_LIST(Table)
|
||||
),
|
||||
?OK.
|
||||
@@ -0,0 +1,32 @@
|
||||
-module(ms_cache_dao_sync_gs_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
|
||||
init([]) ->
|
||||
{
|
||||
ok,
|
||||
{
|
||||
{simple_one_for_one, 5, 10},
|
||||
[?CHILD(ms_cache_dao_sync_gs, worker)]
|
||||
}
|
||||
}.
|
||||
@@ -0,0 +1,190 @@
|
||||
%%% -------------------------------------------------------------------
|
||||
%%% Author : wangyalun
|
||||
%%% Description : dets缓存数据库
|
||||
%%% Created : 2019-01-14
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(ms_cache_ddb).
|
||||
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(TAB_DEFAULT_OPTS, [{ram_copies, [node()]}]).
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
-export([
|
||||
init/0
|
||||
, lookup/2
|
||||
, index_read_keys/3
|
||||
, index_read/3
|
||||
, index_match_object/3
|
||||
, lookup_element/3
|
||||
, update_element/3
|
||||
, insert/2
|
||||
, insert_new/2
|
||||
, delete/2
|
||||
, create_table/2
|
||||
, delete_all_objects/1
|
||||
, table_info/2
|
||||
, foreach/2
|
||||
, foreach/3
|
||||
, foldl/3
|
||||
, all_keys/1
|
||||
, tab2list/1
|
||||
, add_table_copy/3
|
||||
, wait_for_tables/2
|
||||
, member/2
|
||||
, close/1
|
||||
]).
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
lookup(Tab, Key) ->
|
||||
dets:lookup(Tab, Key).
|
||||
|
||||
index_read_keys(Table, Key, Pos) ->
|
||||
FoldlFun =
|
||||
fun(Val, Acc) ->
|
||||
case erlang:element(Pos, Val) of
|
||||
Key ->
|
||||
[erlang:element(2, Val) | Acc];
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end,
|
||||
foldl(Table, FoldlFun, []).
|
||||
|
||||
index_read(Table, Key, Pos) ->
|
||||
FoldlFun =
|
||||
fun(Val, Acc) ->
|
||||
case erlang:element(Pos, Val) of
|
||||
Key ->
|
||||
[Val | Acc];
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end,
|
||||
foldl(Table, FoldlFun, []).
|
||||
|
||||
index_match_object(Table, Pattern, _Pos) ->
|
||||
FoldlFun =
|
||||
fun(Rec, Acc) ->
|
||||
case lists:all(fun({P, K}) -> erlang:element(P, Rec) == K end, Pattern) of
|
||||
true ->
|
||||
[Rec | Acc];
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end,
|
||||
foldl(Table, FoldlFun, []).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
[Record] = dets:lookup(Table, Key),
|
||||
erlang:element(Pos, Record).
|
||||
|
||||
update_element(Table, Key, {Pos, Value}) ->
|
||||
case dets:lookup(Table, Key) of
|
||||
[Record] ->
|
||||
NewRecord = erlang:setelement(Pos, Record, Value),
|
||||
dets:insert(Table, NewRecord),
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
update_element(Table, Key, PosValueList) ->
|
||||
case dets:lookup(Table, Key) of
|
||||
[Record] ->
|
||||
NewRecord = lists:foldl(fun(Pos, Value, Acc) -> erlang:setelement(Pos, Acc, Value) end, Record, PosValueList),
|
||||
dets:insert(Table, NewRecord),
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
insert(Tab, Record) ->
|
||||
dets:insert(Tab, Record).
|
||||
|
||||
insert_new(Tab, Record) ->
|
||||
dets:insert_new(Tab, Record).
|
||||
|
||||
delete(Tab, Key) ->
|
||||
dets:delete(Tab, Key).
|
||||
|
||||
member(Tab, Key) ->
|
||||
dets:member(Tab, Key).
|
||||
|
||||
create_table(Tab, _Opts) ->
|
||||
FileName = "dets/" ++ erlang:atom_to_list(Tab),
|
||||
dets:open_file(Tab, [{type, set}, {keypos, 2}, {file, FileName}]).
|
||||
|
||||
delete_all_objects(Tab) ->
|
||||
dets:delete_all_objects(Tab).
|
||||
|
||||
table_info(Tab, Type) ->
|
||||
dets:info(Tab, Type).
|
||||
|
||||
foreach(Tab, Fun) when is_function(Fun, 1) ->
|
||||
dets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, dets:first(Tab))
|
||||
after
|
||||
dets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table') ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key) ->
|
||||
[Element] = dets:lookup(Tab, Key),
|
||||
Fun(Element),
|
||||
foreach_1(Tab, Fun, dets:next(Tab, Key)).
|
||||
|
||||
foreach(Tab, Fun, Limit) when is_function(Fun, 1) ->
|
||||
dets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, dets:first(Tab), Limit)
|
||||
after
|
||||
dets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table', _N) ->
|
||||
ok;
|
||||
foreach_1(_Tab, _Fun, _, N) when N =< 0 ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key, N) ->
|
||||
[Element] = dets:lookup(Tab, Key),
|
||||
Fun(Element),
|
||||
foreach_1(Tab, Fun, dets:next(Tab, Key), N-1).
|
||||
|
||||
foldl(Tab, Fun, Acc) when is_function(Fun, 2) ->
|
||||
dets:foldl(Fun, Acc, Tab).
|
||||
|
||||
all_keys(Tab) ->
|
||||
MS = ets:fun2ms(fun(Rec) -> element(2, Rec) end),
|
||||
dets:select(Tab, MS).
|
||||
|
||||
tab2list(Tab) ->
|
||||
MS = ets:fun2ms(fun(Rec) -> Rec end),
|
||||
dets:select(Tab, MS).
|
||||
|
||||
add_table_copy(_Tab, _CacheNode, _TabType) ->
|
||||
ok.
|
||||
|
||||
wait_for_tables(_Tables, _TimeOut) ->
|
||||
ok.
|
||||
|
||||
close(Table) ->
|
||||
SyncNum = table_info(Table, size),
|
||||
dets:close(Table),
|
||||
case SyncNum > 0 of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
FileName = "dets/" ++ erlang:atom_to_list(Table),
|
||||
file:delete(FileName)
|
||||
end.
|
||||
@@ -0,0 +1,308 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27. 11月 2023 19:41
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_edb).
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(TAB_DEFAULT_OPTS, [{ram_copies, [node()]}]).
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
-export([
|
||||
init/0
|
||||
, lookup/2
|
||||
, index_read_keys/3
|
||||
, index_read/3
|
||||
, index_match_object/5
|
||||
, lookup_element/3
|
||||
, update_element/3
|
||||
, insert/2
|
||||
, insert_new/2
|
||||
, delete/2
|
||||
, create_table/2
|
||||
, delete_all_objects/1
|
||||
, table_info/2
|
||||
, foreach/2
|
||||
, foreach/3
|
||||
, foldl/3
|
||||
, all_keys/1
|
||||
, tab2list/1
|
||||
, add_table_copy/3
|
||||
, wait_for_tables/2
|
||||
, member/2
|
||||
, close/1
|
||||
, first/1
|
||||
, next/2
|
||||
, last/1
|
||||
, prev/2
|
||||
, slot/2
|
||||
]).
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
lookup(Tab, Key) ->
|
||||
ets:lookup(Tab, Key).
|
||||
|
||||
index_read(Table, Index, Pos) ->
|
||||
RecKeys = index_read_keys(Table, Index, Pos),
|
||||
lists:foldl(
|
||||
fun(RecKey, Acc) ->
|
||||
case ets:lookup(Table, RecKey) of
|
||||
[] ->
|
||||
Acc;
|
||||
[Rec] ->
|
||||
[Rec | Acc]
|
||||
end
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
index_match_object(Table, Index, Pos, Pattern, _Match) ->
|
||||
RecKeys = index_read_keys(Table, Index, Pos),
|
||||
lists:foldl(
|
||||
fun(RecKey, Acc) ->
|
||||
Pattern1 = erlang:setelement(2, Pattern, RecKey),
|
||||
ets:safe_fixtable(Table, true),
|
||||
MatchObjects =
|
||||
try
|
||||
ets:match_object(Table, Pattern1)
|
||||
after
|
||||
ets:safe_fixtable(Table, false)
|
||||
end,
|
||||
MatchObjects ++ Acc
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
ets:lookup_element(Table, Key, Pos).
|
||||
|
||||
update_element(Table, Key, UpdateSpec) ->
|
||||
ets:update_element(Table, Key, UpdateSpec).
|
||||
|
||||
insert(Tab, Record) ->
|
||||
Key = erlang:element(2, Record),
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
ets:insert(Tab, Record),
|
||||
insert_index(Tab, Record);
|
||||
[OldRecord] ->
|
||||
ets:insert(Tab, Record),
|
||||
update_index(Tab, OldRecord, Record)
|
||||
end,
|
||||
true.
|
||||
|
||||
insert_new(Tab, Record) ->
|
||||
case ets:insert_new(Tab, Record) of
|
||||
true ->
|
||||
insert_index(Tab, Record),
|
||||
true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete(Tab, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
skip;
|
||||
[Rec] ->
|
||||
ets:delete(Tab, Key),
|
||||
delete_index(Tab, Rec)
|
||||
end,
|
||||
ok.
|
||||
|
||||
|
||||
member(Tab, Key) ->
|
||||
ets:member(Tab, Key).
|
||||
|
||||
%%create_table(Tab, _Opts) ->
|
||||
%% ets:new(Tab, [public, named_table, set, {keypos, 2}, {read_concurrency, true}]).
|
||||
|
||||
create_table(Tab, Opts) ->
|
||||
TableType = proplists:get_value(type, Opts, set),
|
||||
IndexList1 = proplists:get_value(index, Opts, []),
|
||||
ets:new(Tab, [public, named_table, TableType, {keypos, 2}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
create_index_table(Tab, IndexList1),
|
||||
set_index_list(Tab, IndexList1),
|
||||
ok.
|
||||
|
||||
delete_all_objects(Tab) ->
|
||||
ets:delete_all_objects(Tab),
|
||||
delete_index_all_objects(Tab),
|
||||
ok.
|
||||
|
||||
table_info(Tab, Type) ->
|
||||
ets:info(Tab, Type).
|
||||
|
||||
foreach(Tab, Fun) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab))
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table') ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] -> %% 有可能被其他进程遍历操作删除
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key)).
|
||||
|
||||
foreach(Tab, Fun, Limit) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab), Limit)
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table', _N) ->
|
||||
ok;
|
||||
foreach_1(_Tab, _Fun, _, N) when N =< 0 ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key, N) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] ->
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key), N-1).
|
||||
|
||||
foldl(Tab, Fun, Acc) when is_function(Fun, 2) ->
|
||||
ets:foldl(Fun, Acc, Tab).
|
||||
|
||||
all_keys(Tab) ->
|
||||
MS = ets:fun2ms(fun(Rec) -> element(2, Rec) end),
|
||||
ets:select(Tab, MS).
|
||||
|
||||
tab2list(Tab) ->
|
||||
ets:tab2list(Tab).
|
||||
|
||||
add_table_copy(_Tab, _CacheNode, _TabType) ->
|
||||
ok.
|
||||
|
||||
wait_for_tables(_Tables, _TimeOut) ->
|
||||
ok.
|
||||
|
||||
close(Table) ->
|
||||
ets:delete(Table),
|
||||
IndexList = get_index_list(Table),
|
||||
delete_index_table(Table, IndexList),
|
||||
%% todo 这里没有删除persistent_term里对应的TId的值,理论上走这个逻辑已经退出app,删除的话会触发全量GC(后面再看看怎么实现更好,该存ets?)
|
||||
ok.
|
||||
|
||||
first(Table) ->
|
||||
ets:first(Table).
|
||||
|
||||
next(Table, PrevKey) ->
|
||||
ets:next(Table, PrevKey).
|
||||
|
||||
last(Table) ->
|
||||
ets:last(Table).
|
||||
|
||||
prev(Table, Key) ->
|
||||
ets:prev(Table, Key).
|
||||
|
||||
slot(TableName, Slot) ->
|
||||
ets:slot(TableName, Slot).
|
||||
|
||||
create_index_table(Table, [Pos | T]) ->
|
||||
TId = ets:new(table_index, [public, ordered_set, {keypos, 1}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
ms_cache_config:put({?MODULE, index_table_name, Table, Pos}, TId),
|
||||
create_index_table(Table, T);
|
||||
create_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
delete_index_table(Table, [Pos | T]) ->
|
||||
TId = get_index_table(Table, Pos),
|
||||
ets:delete(TId),
|
||||
delete_index_table(Table, T);
|
||||
delete_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
get_index_table(Table, Pos) ->
|
||||
ms_cache_config:get({?MODULE, index_table_name, Table, Pos}, undefined).
|
||||
|
||||
|
||||
set_index_list(Tab, IndexList) ->
|
||||
ms_cache_config:put({?MODULE, index_list, Tab}, IndexList).
|
||||
|
||||
get_index_list(Tab) ->
|
||||
ms_cache_config:get({?MODULE, index_list, Tab}, []).
|
||||
|
||||
insert_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
ets:insert(IndexTable, {{Index, RecKey}})
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
ets:delete(IndexTable, {Index, RecKey})
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
update_index(Table, OldRec, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
OldIndex = erlang:element(IndexPos, OldRec),
|
||||
NewIndex = erlang:element(IndexPos, Rec),
|
||||
case OldIndex /= NewIndex of
|
||||
true ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
%% 先插入后删除,模拟保证原子性,查询到的是删除前或者删除后的数据,不会有中间态
|
||||
ets:insert(IndexTable, {{NewIndex, RecKey}}),
|
||||
ets:delete(IndexTable, {OldIndex, RecKey});
|
||||
false ->
|
||||
skip
|
||||
end
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index_all_objects(Table) ->
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
ets:delete_all_objects(IndexTable)
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
index_read_keys(Table, IxKey, Pos) ->
|
||||
IndexTable = get_index_table(Table, Pos),
|
||||
Pat = [{{{IxKey, '$1'}}, [], ['$1']}],
|
||||
ets:select(IndexTable, Pat).
|
||||
@@ -0,0 +1,300 @@
|
||||
%%% -------------------------------------------------------------------
|
||||
%%% Author : wangyalun
|
||||
%%% Description : ets缓存数据库
|
||||
%%% Created : 2019-01-14
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(ms_cache_edb2).
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(TAB_DEFAULT_OPTS, [{ram_copies, [node()]}]).
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
-export([
|
||||
init/0
|
||||
, lookup/2
|
||||
, index_read/3
|
||||
, index_match_object/5
|
||||
, lookup_element/3
|
||||
, insert/2
|
||||
, insert_new/2
|
||||
, delete/2
|
||||
, create_table/2
|
||||
%% , create_table/3
|
||||
, delete_all_objects/1
|
||||
, table_info/2
|
||||
, foreach/2
|
||||
, foreach/3
|
||||
, foldl/3
|
||||
, all_keys/1
|
||||
, tab2list/1
|
||||
, add_table_copy/3
|
||||
, wait_for_tables/2
|
||||
, member/2
|
||||
, close/1
|
||||
, first/1
|
||||
, next/2
|
||||
, slot/2
|
||||
]).
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
lookup(Tab, Key) ->
|
||||
ets:lookup(Tab, Key).
|
||||
|
||||
index_read(Table, Index, Pos) ->
|
||||
IndexTable = get_index_table(Table, Pos),
|
||||
%% RecKeys = safe_lookup_element(IndexTable, {Pos, Index}, 2),
|
||||
RecKeys = ets:lookup(IndexTable, Index),
|
||||
lists:foldl(
|
||||
fun({_, RecKey}, Acc) ->
|
||||
case ets:lookup(Table, RecKey) of
|
||||
[] ->
|
||||
Acc;
|
||||
[Rec] ->
|
||||
[Rec | Acc]
|
||||
end
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
index_match_object(Table, Index, Pos, _Pattern, Match) ->
|
||||
IndexTable = get_index_table(Table, Pos),
|
||||
%% RecKeys = safe_lookup_element(IndexTable, {Pos, Index}, 2),
|
||||
RecKeys = ets:lookup(IndexTable, Index),
|
||||
lists:foldl(
|
||||
fun({_, RecKey}, Acc) ->
|
||||
case ets:lookup(Table, RecKey) of
|
||||
[] ->
|
||||
Acc;
|
||||
[Rec] ->
|
||||
case lists:all(fun({P, K}) -> erlang:element(P, Rec) == K end, Match) of
|
||||
true ->
|
||||
[Rec | Acc];
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
ets:lookup_element(Table, Key, Pos).
|
||||
|
||||
insert(Tab, Record) ->
|
||||
Key = erlang:element(2, Record),
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
ets:insert(Tab, Record),
|
||||
insert_index(Tab, Record);
|
||||
[OldRecord] ->
|
||||
ets:insert(Tab, Record),
|
||||
update_index(Tab, OldRecord, Record)
|
||||
end,
|
||||
true.
|
||||
|
||||
insert_new(Tab, Record) ->
|
||||
case ets:insert_new(Tab, Record) of
|
||||
true ->
|
||||
insert_index(Tab, Record),
|
||||
true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete(Tab, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
skip;
|
||||
[Rec] ->
|
||||
ets:delete(Tab, Key),
|
||||
delete_index(Tab, Rec)
|
||||
end,
|
||||
ok.
|
||||
|
||||
|
||||
member(Tab, Key) ->
|
||||
ets:member(Tab, Key).
|
||||
|
||||
%%create_table(Tab, _Opts) ->
|
||||
%% ets:new(Tab, [public, named_table, set, {keypos, 2}, {read_concurrency, true}]).
|
||||
|
||||
create_table(Tab, Opts) ->
|
||||
TableType = proplists:get_value(type, Opts, set),
|
||||
IndexList1 = proplists:get_value(index, Opts, []),
|
||||
ets:new(Tab, [public, named_table, TableType, {keypos, 2}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
%% IndexTable = get_index_table(Tab),
|
||||
%% ets:new(IndexTable, [public, named_table, bag, {keypos, 1}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
create_index_table(Tab, IndexList1),
|
||||
set_index_list(Tab, IndexList1),
|
||||
ok.
|
||||
|
||||
delete_all_objects(Tab) ->
|
||||
ets:delete_all_objects(Tab),
|
||||
delete_index_all_objects(Tab).
|
||||
|
||||
table_info(Tab, Type) ->
|
||||
ets:info(Tab, Type).
|
||||
|
||||
foreach(Tab, Fun) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab))
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table') ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] -> %% 有可能被其他进程遍历操作删除
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key)).
|
||||
|
||||
foreach(Tab, Fun, Limit) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab), Limit)
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table', _N) ->
|
||||
ok;
|
||||
foreach_1(_Tab, _Fun, _, N) when N =< 0 ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key, N) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] ->
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key), N-1).
|
||||
|
||||
foldl(Tab, Fun, Acc) when is_function(Fun, 2) ->
|
||||
ets:foldl(Fun, Acc, Tab).
|
||||
|
||||
all_keys(Tab) ->
|
||||
MS = ets:fun2ms(fun(Rec) -> element(2, Rec) end),
|
||||
ets:select(Tab, MS).
|
||||
|
||||
tab2list(Tab) ->
|
||||
ets:tab2list(Tab).
|
||||
|
||||
add_table_copy(_Tab, _CacheNode, _TabType) ->
|
||||
ok.
|
||||
|
||||
wait_for_tables(_Tables, _TimeOut) ->
|
||||
ok.
|
||||
|
||||
close(Table) ->
|
||||
ets:delete(Table),
|
||||
IndexList = get_index_list(Table),
|
||||
delete_index_table(Table, IndexList),
|
||||
ok.
|
||||
|
||||
first(Table) ->
|
||||
ets:first(Table).
|
||||
|
||||
next(Table, PrevKey) ->
|
||||
ets:next(Table, PrevKey).
|
||||
|
||||
slot(TableName, Slot) ->
|
||||
ets:slot(TableName, Slot).
|
||||
|
||||
create_index_table(Table, [Pos | T]) ->
|
||||
TId = ets:new(table_index, [public, bag, {keypos, 1}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
persistent_term:put({?MODULE, index_table_name, Table, Pos}, TId),
|
||||
create_index_table(Table, T);
|
||||
create_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
delete_index_table(Table, [Pos | T]) ->
|
||||
TId = get_index_table(Table, Pos),
|
||||
ets:delete(TId),
|
||||
delete_index_table(Table, T);
|
||||
delete_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
get_index_table(Table, Pos) ->
|
||||
persistent_term:get({?MODULE, index_table_name, Table, Pos}, undefined).
|
||||
|
||||
set_index_list(Tab, IndexList) ->
|
||||
persistent_term:put({?MODULE, index_list, Tab}, IndexList).
|
||||
|
||||
get_index_list(Tab) ->
|
||||
persistent_term:get({?MODULE, index_list, Tab}, []).
|
||||
|
||||
insert_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
ets:insert(IndexTable, {Index, RecKey})
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
ets:delete_object(IndexTable, {Index, RecKey})
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
update_index(Table, OldRec, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
OldIndex = erlang:element(IndexPos, OldRec),
|
||||
NewIndex = erlang:element(IndexPos, Rec),
|
||||
case OldIndex /= NewIndex of
|
||||
true ->
|
||||
%% 先插入后删除,模拟保证原子性,查询到的是删除前或者删除后的数据,不会有中间态
|
||||
ets:insert(IndexTable, {NewIndex, RecKey}),
|
||||
ets:delete_object(IndexTable, {OldIndex, RecKey});
|
||||
false ->
|
||||
skip
|
||||
end
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index_all_objects(Table) ->
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
ets:delete_all_objects(IndexTable)
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
%%safe_lookup_element(Table, Key, Pos) ->
|
||||
%% try
|
||||
%% ets:lookup_element(Table, Key, Pos)
|
||||
%% catch _:_ ->
|
||||
%% []
|
||||
%% end.
|
||||
@@ -0,0 +1,316 @@
|
||||
%%% -------------------------------------------------------------------
|
||||
%%% Author : wangyalun
|
||||
%%% Description : ets缓存数据库
|
||||
%%% Created : 2019-01-14
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(ms_cache_edb3).
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-define(TAB_DEFAULT_OPTS, [{ram_copies, [node()]}]).
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
-export([
|
||||
init/0
|
||||
, lookup/2
|
||||
, index_read/3
|
||||
, index_match_object/5
|
||||
, lookup_element/3
|
||||
, insert/2
|
||||
, insert_new/2
|
||||
, delete/2
|
||||
, create_table/2
|
||||
%% , create_table/3
|
||||
, delete_all_objects/1
|
||||
, table_info/2
|
||||
, foreach/2
|
||||
, foreach/3
|
||||
, foldl/3
|
||||
, all_keys/1
|
||||
, tab2list/1
|
||||
, add_table_copy/3
|
||||
, wait_for_tables/2
|
||||
, member/2
|
||||
, close/1
|
||||
, first/1
|
||||
, next/2
|
||||
, slot/2
|
||||
]).
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
lookup(Tab, Key) ->
|
||||
ets:lookup(Tab, Key).
|
||||
|
||||
index_read(Table, Index, Pos) ->
|
||||
IndexTable = get_index_table(Table, Pos),
|
||||
RecKeys = safe_lookup_element(IndexTable, Index, 2),
|
||||
lists:foldl(
|
||||
fun(RecKey, Acc) ->
|
||||
case ets:lookup(Table, RecKey) of
|
||||
[] ->
|
||||
Acc;
|
||||
[Rec] ->
|
||||
[Rec | Acc]
|
||||
end
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
index_match_object(Table, Index, Pos, _Pattern, Match) ->
|
||||
IndexTable = get_index_table(Table, Pos),
|
||||
RecKeys = safe_lookup_element(IndexTable, Index, 2),
|
||||
lists:foldl(
|
||||
fun(RecKey, Acc) ->
|
||||
case ets:lookup(Table, RecKey) of
|
||||
[] ->
|
||||
Acc;
|
||||
[Rec] ->
|
||||
case lists:all(fun({P, K}) -> erlang:element(P, Rec) == K end, Match) of
|
||||
true ->
|
||||
[Rec | Acc];
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end
|
||||
end, [], RecKeys
|
||||
).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
ets:lookup_element(Table, Key, Pos).
|
||||
|
||||
insert(Tab, Record) ->
|
||||
Key = erlang:element(2, Record),
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
ets:insert(Tab, Record),
|
||||
insert_index(Tab, Record);
|
||||
[OldRecord] ->
|
||||
ets:insert(Tab, Record),
|
||||
update_index(Tab, OldRecord, Record)
|
||||
end,
|
||||
true.
|
||||
|
||||
insert_new(Tab, Record) ->
|
||||
case ets:insert_new(Tab, Record) of
|
||||
true ->
|
||||
insert_index(Tab, Record),
|
||||
true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete(Tab, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[] ->
|
||||
skip;
|
||||
[Rec] ->
|
||||
ets:delete(Tab, Key),
|
||||
delete_index(Tab, Rec)
|
||||
end,
|
||||
ok.
|
||||
|
||||
|
||||
member(Tab, Key) ->
|
||||
ets:member(Tab, Key).
|
||||
|
||||
%%create_table(Tab, _Opts) ->
|
||||
%% ets:new(Tab, [public, named_table, set, {keypos, 2}, {read_concurrency, true}]).
|
||||
|
||||
create_table(Tab, Opts) ->
|
||||
TableType = proplists:get_value(type, Opts, set),
|
||||
IndexList1 = proplists:get_value(index, Opts, []),
|
||||
ets:new(Tab, [public, named_table, TableType, {keypos, 2}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
create_index_table(Tab, IndexList1),
|
||||
set_index_list(Tab, IndexList1),
|
||||
ok.
|
||||
|
||||
delete_all_objects(Tab) ->
|
||||
ets:delete_all_objects(Tab),
|
||||
delete_index_all_objects(Tab).
|
||||
|
||||
table_info(Tab, Type) ->
|
||||
ets:info(Tab, Type).
|
||||
|
||||
foreach(Tab, Fun) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab))
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table') ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] -> %% 有可能被其他进程遍历操作删除
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key)).
|
||||
|
||||
foreach(Tab, Fun, Limit) when is_function(Fun, 1) ->
|
||||
ets:safe_fixtable(Tab, true),
|
||||
try
|
||||
foreach_1(Tab, Fun, ets:first(Tab), Limit)
|
||||
after
|
||||
ets:safe_fixtable(Tab, false)
|
||||
end,
|
||||
ok.
|
||||
|
||||
foreach_1(_Tab, _Fun, '$end_of_table', _N) ->
|
||||
ok;
|
||||
foreach_1(_Tab, _Fun, _, N) when N =< 0 ->
|
||||
ok;
|
||||
foreach_1(Tab, Fun, Key, N) ->
|
||||
case ets:lookup(Tab, Key) of
|
||||
[Element] ->
|
||||
Fun(Element);
|
||||
[] ->
|
||||
skip
|
||||
end,
|
||||
foreach_1(Tab, Fun, ets:next(Tab, Key), N-1).
|
||||
|
||||
foldl(Tab, Fun, Acc) when is_function(Fun, 2) ->
|
||||
ets:foldl(Fun, Acc, Tab).
|
||||
|
||||
all_keys(Tab) ->
|
||||
MS = ets:fun2ms(fun(Rec) -> element(2, Rec) end),
|
||||
ets:select(Tab, MS).
|
||||
|
||||
tab2list(Tab) ->
|
||||
ets:tab2list(Tab).
|
||||
|
||||
add_table_copy(_Tab, _CacheNode, _TabType) ->
|
||||
ok.
|
||||
|
||||
wait_for_tables(_Tables, _TimeOut) ->
|
||||
ok.
|
||||
|
||||
close(Table) ->
|
||||
ets:delete(Table),
|
||||
IndexList = get_index_list(Table),
|
||||
delete_index_table(Table, IndexList),
|
||||
ok.
|
||||
|
||||
first(Table) ->
|
||||
ets:first(Table).
|
||||
|
||||
next(Table, PrevKey) ->
|
||||
ets:next(Table, PrevKey).
|
||||
|
||||
slot(TableName, Slot) ->
|
||||
ets:slot(TableName, Slot).
|
||||
|
||||
create_index_table(Table, [Pos | T]) ->
|
||||
TId = ets:new(table_index, [public, ordered_set, {keypos, 1}, {read_concurrency, true}, {write_concurrency, true}]),
|
||||
persistent_term:put({?MODULE, index_table_name, Table, Pos}, TId),
|
||||
create_index_table(Table, T);
|
||||
create_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
delete_index_table(Table, [Pos | T]) ->
|
||||
TId = get_index_table(Table, Pos),
|
||||
ets:delete(TId),
|
||||
delete_index_table(Table, T);
|
||||
delete_index_table(_Table, []) ->
|
||||
?OK.
|
||||
|
||||
get_index_table(Table, Pos) ->
|
||||
persistent_term:get({?MODULE, index_table_name, Table, Pos}, undefined).
|
||||
|
||||
set_index_list(Tab, IndexList) ->
|
||||
persistent_term:put({?MODULE, index_list, Tab}, IndexList).
|
||||
|
||||
get_index_list(Tab) ->
|
||||
persistent_term:get({?MODULE, index_list, Tab}, []).
|
||||
|
||||
insert_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
case ets:lookup(IndexTable, Index) of
|
||||
[] ->
|
||||
ets:insert(IndexTable, {Index, [RecKey]});
|
||||
[{_, OldKeys}] -> %% todo 忽略重复key,业务保证
|
||||
ets:insert(IndexTable, {Index, [RecKey | OldKeys]})
|
||||
end
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index(Table, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
Index = erlang:element(IndexPos, Rec),
|
||||
case ets:lookup(IndexTable, Index) of
|
||||
[] ->
|
||||
?SKIP;
|
||||
[{_, OldKeys}] ->
|
||||
ets:insert(IndexTable, {Index, lists:delete(RecKey, OldKeys)})
|
||||
end
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
update_index(Table, OldRec, Rec) ->
|
||||
RecKey = erlang:element(2, Rec),
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
OldIndex = erlang:element(IndexPos, OldRec),
|
||||
NewIndex = erlang:element(IndexPos, Rec),
|
||||
case OldIndex /= NewIndex of
|
||||
true ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
%% 先插入后删除,模拟保证原子性,查询到的是删除前或者删除后的数据,不会有中间态
|
||||
case ets:lookup(IndexTable, NewIndex) of
|
||||
[] ->
|
||||
ets:insert(IndexTable, {NewIndex, [RecKey]});
|
||||
[{_, OldKeys}] -> %% todo 忽略重复key,业务保证
|
||||
ets:insert(IndexTable, {NewIndex, [RecKey | OldKeys]})
|
||||
end,
|
||||
case ets:lookup(IndexTable, OldIndex) of
|
||||
[] ->
|
||||
?SKIP;
|
||||
[{_, OldKeys1}] ->
|
||||
ets:insert(IndexTable, {OldIndex, lists:delete(RecKey, OldKeys1)})
|
||||
end;
|
||||
false ->
|
||||
?SKIP
|
||||
end
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
delete_index_all_objects(Table) ->
|
||||
IndexList = get_index_list(Table),
|
||||
lists:foreach(
|
||||
fun(IndexPos) ->
|
||||
IndexTable = get_index_table(Table, IndexPos),
|
||||
ets:delete_all_objects(IndexTable)
|
||||
end, IndexList
|
||||
),
|
||||
ok.
|
||||
|
||||
safe_lookup_element(Table, Key, Pos) ->
|
||||
try
|
||||
ets:lookup_element(Table, Key, Pos)
|
||||
catch _:_ ->
|
||||
[]
|
||||
end.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
-module(ms_cache_gs_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
|
||||
init([]) ->
|
||||
{
|
||||
ok,
|
||||
{
|
||||
{simple_one_for_one, 5, 10},
|
||||
[?CHILD(ms_cache_gs_table_sup, supervisor)]
|
||||
}
|
||||
}.
|
||||
@@ -0,0 +1,32 @@
|
||||
-module(ms_cache_gs_table_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/1]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
|
||||
start_link(SupName) ->
|
||||
supervisor:start_link({local, SupName}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
|
||||
init([]) ->
|
||||
{
|
||||
ok,
|
||||
{
|
||||
{simple_one_for_one, 5, 10},
|
||||
[?CHILD(ms_cache_gs, worker)]
|
||||
}
|
||||
}.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
||||
%%% -------------------------------------------------------------------
|
||||
%%% Author : wangyalun
|
||||
%%% Description : 缓存mdb方法 todo 目前已经弃用
|
||||
%%% Created : 2019-01-14
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(ms_cache_mdb).
|
||||
-include("base.hrl").
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%% ====================================================================
|
||||
%% Include files
|
||||
%% ====================================================================
|
||||
-define(TAB_DEFAULT_OPTS, [{ram_copies, []}]).
|
||||
|
||||
%% ====================================================================
|
||||
%% External exports
|
||||
%% ====================================================================
|
||||
-export([
|
||||
init/0
|
||||
, lookup/2
|
||||
, index_read_keys/3
|
||||
, index_read/3
|
||||
, index_match_object/5
|
||||
, lookup_element/3
|
||||
, update_element/3
|
||||
, insert/2
|
||||
, insert_new/2
|
||||
, delete/2
|
||||
, create_table/2
|
||||
%% , create_table/3
|
||||
, delete_all_objects/1
|
||||
, table_info/2
|
||||
, foreach/2
|
||||
, foreach/3
|
||||
, foldl/3
|
||||
, all_keys/1
|
||||
, tab2list/1
|
||||
, add_table_copy/3
|
||||
, wait_for_tables/2
|
||||
, member/2
|
||||
, is_table_exist/1
|
||||
, close/1
|
||||
, first/1
|
||||
, next/2
|
||||
, last/1
|
||||
, prev/2
|
||||
, slot/2
|
||||
]).
|
||||
|
||||
init() ->
|
||||
mnesia:stop(),
|
||||
mnesia:start(),
|
||||
ok.
|
||||
|
||||
lookup(Table, Key) ->
|
||||
mnesia:dirty_read(Table, Key).
|
||||
|
||||
index_read_keys(Table, Key, Pos) ->
|
||||
[element(2, Record) || Record <- mnesia:dirty_index_read(Table, Key, Pos)].
|
||||
|
||||
index_read(Table, Key, Pos) ->
|
||||
mnesia:dirty_index_read(Table, Key, Pos).
|
||||
|
||||
index_match_object(Table, _Index, Pos, Pattern, _Match) ->
|
||||
mnesia:dirty_index_match_object(Table, Pattern, Pos).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
[Record] = mnesia:dirty_read(Table, Key),
|
||||
erlang:element(Pos, Record).
|
||||
|
||||
update_element(Table, Key, {Pos, Value}) ->
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[Record] ->
|
||||
NewRecord = erlang:setelement(Pos, Record, Value),
|
||||
mnesia:dirty_write(Table, NewRecord),
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
update_element(Table, Key, PosValueList) ->
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[Record] ->
|
||||
NewRecord = lists:foldl(fun(Pos, Value, Acc) -> erlang:setelement(Pos, Acc, Value) end, Record, PosValueList),
|
||||
mnesia:dirty_write(Table, NewRecord),
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
insert(Table, Record) ->
|
||||
mnesia:dirty_write(Table, Record).
|
||||
|
||||
insert_new(Table, Record) ->
|
||||
Key = erlang:element(2, Record),
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[] ->
|
||||
mnesia:dirty_write(Table, Record),
|
||||
true;
|
||||
[_] ->
|
||||
false
|
||||
end.
|
||||
|
||||
delete(Table, Key) ->
|
||||
mnesia:dirty_delete(Table, Key).
|
||||
|
||||
member(Table, Key) ->
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[] ->
|
||||
false;
|
||||
[_] ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% @doc
|
||||
%% 创建一张mnesia表
|
||||
%%
|
||||
-spec create_table(TableName :: atom(), Options :: list()) -> ok.
|
||||
create_table(TableName, Options1) ->
|
||||
%% IndexList =
|
||||
%% lists:usort([
|
||||
%% begin
|
||||
%% case erlang:is_tuple(Index) of
|
||||
%% true ->
|
||||
%% erlang:element(1, Index);
|
||||
%% false ->
|
||||
%% Index
|
||||
%% end
|
||||
%% end || Index <- Handler:index_list()
|
||||
%% ]),
|
||||
%%
|
||||
%% IndexList1 =
|
||||
%% case Handler:key_list() of
|
||||
%% [FirstKey | _] ->
|
||||
%% [FirstKey | lists:delete(FirstKey, IndexList)];
|
||||
%% _ ->
|
||||
%% IndexList
|
||||
%% end,
|
||||
%% RecName = Handler:record_name(),
|
||||
%% DefaultOptions = [{type, set}, {attributes, Handler:field_list()}, {record_name, RecName}, {index, IndexList1},
|
||||
%% {ram_copies, [erlang:node()]},
|
||||
%% {storage_properties, [{ets, [{read_concurrency,true}, {write_concurrency,true}]}]}
|
||||
%% ],
|
||||
%% Options1 =
|
||||
%% lists:foldl(
|
||||
%% fun({Key, Val}, Acc) ->
|
||||
%% lists:keystore(Key, 1, Acc, {Key, Val})
|
||||
%% end, DefaultOptions, Options),
|
||||
|
||||
{atomic, ok} = mnesia:create_table(TableName, Options1),
|
||||
?OK.
|
||||
|
||||
%%create_table(Table, Opts) ->
|
||||
%% {ok, NewOpts} = merge_options(?TAB_DEFAULT_OPTS, Opts),
|
||||
%% {atomic, ok} = mnesia:create_table(Table, NewOpts),
|
||||
%% ok.
|
||||
|
||||
|
||||
delete_all_objects(Table) ->
|
||||
mnesia:clear_table(Table).
|
||||
|
||||
table_info(Table, Type) ->
|
||||
mnesia:table_info(Table, Type).
|
||||
|
||||
foreach(Table, Fun) when is_function(Fun, 1) ->
|
||||
safe_fixtable(Table, ?TRUE),
|
||||
try
|
||||
foreach_1(Table, Fun, mnesia:dirty_first(Table))
|
||||
after
|
||||
safe_fixtable(Table, ?FALSE)
|
||||
end.
|
||||
|
||||
foreach_1(_Table, _Fun, '$end_of_table') ->
|
||||
ok;
|
||||
foreach_1(Table, Fun, Key) ->
|
||||
NextKey = mnesia:dirty_next(Table, Key),
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[Element] ->
|
||||
Fun(Element);
|
||||
[] -> %% 有可能被其他进程遍历操作删除了
|
||||
?SKIP
|
||||
end,
|
||||
foreach_1(Table, Fun, NextKey).
|
||||
|
||||
foreach(Table, Fun, Limit) when is_function(Fun, 1) ->
|
||||
safe_fixtable(Table, ?TRUE),
|
||||
try
|
||||
foreach_1(Table, Fun, mnesia:dirty_first(Table), Limit)
|
||||
after
|
||||
safe_fixtable(Table, ?FALSE)
|
||||
end.
|
||||
|
||||
foreach_1(_Table, _Fun, '$end_of_table', _N) ->
|
||||
ok;
|
||||
foreach_1(_Table, _Fun, _, N) when N =< 0 ->
|
||||
ok;
|
||||
foreach_1(Table, Fun, Key, N) ->
|
||||
NextKey = mnesia:dirty_next(Table, Key),
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[Element] ->
|
||||
Fun(Element);
|
||||
[] -> %% 有可能被其他进程遍历操作删除了
|
||||
?SKIP
|
||||
end,
|
||||
foreach_1(Table, Fun, NextKey, N-1).
|
||||
|
||||
foldl(Table, Fun, Acc) when is_function(Fun, 2) ->
|
||||
safe_fixtable(Table, ?TRUE),
|
||||
try
|
||||
foldl(Table, Fun, Acc, mnesia:dirty_first(Table))
|
||||
after
|
||||
safe_fixtable(Table, ?FALSE)
|
||||
end.
|
||||
|
||||
foldl(_Table, _Fun, Acc, '$end_of_table') ->
|
||||
Acc;
|
||||
foldl(Table, Fun, Acc, Key) ->
|
||||
NextKey = mnesia:dirty_next(Table, Key),
|
||||
foldl(Table, Fun, lists:foldl(Fun, Acc, mnesia:dirty_read(Table, Key)), NextKey).
|
||||
|
||||
|
||||
all_keys(Table) ->
|
||||
mnesia:dirty_all_keys(Table).
|
||||
|
||||
tab2list(Table) ->
|
||||
tab2list_1(Table, mnesia:dirty_first(Table), []).
|
||||
|
||||
tab2list_1(_Table, '$end_of_table', Acc) ->
|
||||
Acc;
|
||||
tab2list_1(Table, Key, Acc) ->
|
||||
case mnesia:dirty_read(Table, Key) of
|
||||
[Rec] ->
|
||||
tab2list_1(Table, mnesia:dirty_next(Table, Key), [Rec | Acc]);
|
||||
[] -> %% 有可能被其他进程遍历操作删除了
|
||||
tab2list_1(Table, mnesia:dirty_next(Table, Key), Acc)
|
||||
end.
|
||||
|
||||
add_table_copy(Table, CacheNode, TableType) ->
|
||||
mnesia:add_table_copy(Table, CacheNode, TableType).
|
||||
|
||||
wait_for_tables(Tables, TimeOut) ->
|
||||
mnesia:wait_for_tables(Tables, TimeOut).
|
||||
|
||||
close(Table) ->
|
||||
mnesia:delete_table(Table),
|
||||
ok.
|
||||
|
||||
%%merge_options([{ram_copies, _} = Opt | T], Acc) ->
|
||||
%% case lists:keymember(ram_copies, 1, Acc) orelse
|
||||
%% lists:keymember(disc_copies, 1, Acc) orelse
|
||||
%% lists:keymember(disc_only_copies, 1, Acc) of
|
||||
%% true ->
|
||||
%% merge_options(T, Acc);
|
||||
%% false ->
|
||||
%% merge_options(T, [Opt | Acc])
|
||||
%% end;
|
||||
%%merge_options([Opt | T], Acc) ->
|
||||
%% case lists:keymember(element(1, Opt), 1, Acc) of
|
||||
%% true ->
|
||||
%% merge_options(T, Acc);
|
||||
%% false ->
|
||||
%% merge_options(T, [Opt | Acc])
|
||||
%% end;
|
||||
%%merge_options([], Acc) ->
|
||||
%% {ok, Acc}.
|
||||
|
||||
is_table_exist(Table) ->
|
||||
Tables = mnesia:system_info(tables),
|
||||
lists:member(Table, Tables).
|
||||
|
||||
safe_fixtable(Table, Bool) ->
|
||||
?DEBUG(?MODULE, "StorageType = ~p, Table = ~p, Bool = ~p", [mnesia:table_info(Table, storage_type), Table, Bool]),
|
||||
mnesia_lib:db_fixtable(mnesia:table_info(Table, storage_type), Table, Bool).
|
||||
|
||||
first(Table) ->
|
||||
mnesia:dirty_first(Table).
|
||||
|
||||
next(Table, PrevKey) ->
|
||||
mnesia:dirty_next(Table, PrevKey).
|
||||
|
||||
last(Table) ->
|
||||
mnesia:dirty_last(Table).
|
||||
|
||||
prev(Table, NextKey) ->
|
||||
mnesia:dirty_prev(Table, NextKey).
|
||||
|
||||
slot(Table, Slot) ->
|
||||
mnesia:dirty_slot(Table, Slot).
|
||||
@@ -0,0 +1,183 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2023, <COMPANY>
|
||||
%%% @doc
|
||||
%%% 补丁进程
|
||||
%%% @end
|
||||
%%% Created : 08. 9月 2023 15:33
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_patch_lib).
|
||||
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
-include("base.hrl").
|
||||
-export([patch/2]).
|
||||
|
||||
-define(TABLE_PATCH_VERSION, '$patch_version$').
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
%% 执行补丁程序
|
||||
patch(DbPool, SyncOptions) ->
|
||||
case is_initialized(DbPool) of
|
||||
true ->
|
||||
first_init(DbPool, SyncOptions);
|
||||
false ->
|
||||
case SyncOptions of
|
||||
#{patch_run_mod := auto} ->
|
||||
ok = run_patch(DbPool, SyncOptions);
|
||||
#{patch_run_mod := {env, Evn}} ->
|
||||
?IF(proplists:is_defined(Evn, os:env()), run_patch(DbPool, SyncOptions), ok);
|
||||
#{} ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
%% 是否已初始化
|
||||
is_initialized(DbPool) ->
|
||||
SQL = "SHOW TABLES;",
|
||||
{ok, _Column, Tables} = ms_cache:execute(DbPool, SQL),
|
||||
not lists:member([erlang:atom_to_binary(?TABLE_PATCH_VERSION)], Tables).
|
||||
|
||||
%% 首次初始化
|
||||
first_init(DbPool, SyncOptions) ->
|
||||
case maps:get(patch_prefix, SyncOptions, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
ok;
|
||||
PatchPrefix ->
|
||||
init_patch_version(DbPool),
|
||||
insert_init_data(DbPool, PatchPrefix)
|
||||
end.
|
||||
|
||||
%% 初始化打补丁相关程序
|
||||
init_patch_version(DbPool) ->
|
||||
SQL = io_lib:format("CREATE TABLE IF NOT EXISTS `~s` (
|
||||
`version` int(11) NOT NULL DEFAULT '0',
|
||||
`func_list` text NOT NULL,
|
||||
PRIMARY KEY (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", [?TABLE_PATCH_VERSION]),
|
||||
ok = mysql_poolboy:query(DbPool, SQL).
|
||||
|
||||
%% 插入初始数据
|
||||
insert_init_data(DbPool, PatchPrefix) ->
|
||||
case get_mod_list(PatchPrefix, 0) of
|
||||
[] ->
|
||||
ok;
|
||||
L ->
|
||||
{Version, Mod} = lists:last(L),
|
||||
SQL = io_lib:format("INSERT INTO `~s` (`version`, `func_list`) VALUES(?, ?);", [?TABLE_PATCH_VERSION]),
|
||||
Exports = proplists:delete(module_info, erlang:apply(Mod, module_info, [exports])),
|
||||
ok = mysql_poolboy:query(DbPool, SQL, [Version, term_to_string(Exports)])
|
||||
end.
|
||||
|
||||
%% 执行补丁程序
|
||||
run_patch(DbPool, SyncOptions) ->
|
||||
case maps:get(patch_prefix, SyncOptions, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
ok;
|
||||
PatchPrefix ->
|
||||
Limit = maps:get(limit, SyncOptions, 1),
|
||||
{LatestVersion, Maps} = load_patch_version_table(DbPool, Limit),
|
||||
VersionModList = get_mod_list(PatchPrefix, LatestVersion),
|
||||
do_patch(DbPool, VersionModList, Maps)
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% 载入表数据
|
||||
load_patch_version_table(DbPool, Limit) ->
|
||||
SQL = io_lib:format("SELECT * FROM `~s` ORDER BY `version` DESC LIMIT ?;", [?TABLE_PATCH_VERSION]),
|
||||
case mysql_poolboy:query(DbPool, SQL, [Limit]) of
|
||||
{ok, _Column, []} ->
|
||||
{0, #{}};
|
||||
{ok, _Column, Rows} ->
|
||||
[LatestVersion | _] = lists:last(Rows),
|
||||
L = [{Version, string_to_term(FuncList)} || [Version, FuncList] <- Rows],
|
||||
{LatestVersion, maps:from_list(L)}
|
||||
end.
|
||||
|
||||
%% 获取补丁模块名
|
||||
get_mod_list(PatchPrefix, LatestVersion) ->
|
||||
case application:get_application() of
|
||||
{ok, AppName} ->
|
||||
{ok, Pattern} = re:compile(PatchPrefix ++ "([0-9]{8})"),
|
||||
L = lists:filtermap(
|
||||
fun(Filename) ->
|
||||
Basename = filename:basename(Filename, ".beam"),
|
||||
case re:run(Basename, Pattern, [{capture, all_but_first, list}]) of
|
||||
{match, [StrVersion]} ->
|
||||
Version = erlang:list_to_integer(StrVersion),
|
||||
?IF(Version >= LatestVersion, {true, {Version, erlang:list_to_atom(Basename)}}, false);
|
||||
nomatch ->
|
||||
false
|
||||
end
|
||||
end, filelib:wildcard("*.beam", code:lib_dir(AppName) ++ "/ebin")),
|
||||
lists:sort(L);
|
||||
undefined ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% 执行打补丁
|
||||
do_patch(DbPool, [{Version, Mod} | T], Maps) ->
|
||||
Exports = proplists:delete(module_info, erlang:apply(Mod, module_info, [exports])),
|
||||
case Maps of
|
||||
#{Version := FuncList} ->
|
||||
case Exports -- FuncList of
|
||||
[] ->
|
||||
ok;
|
||||
ExecuteFuncList ->
|
||||
?INFO(?MODULE, "append execute patch version:~p total number of functions:~p~n", [Version, erlang:length(ExecuteFuncList)]),
|
||||
do_patch_1(DbPool, Version, Mod, ExecuteFuncList)
|
||||
end;
|
||||
#{} ->
|
||||
?INFO(?MODULE, "execute patch version:~p total number of functions:~p~n", [Version, erlang:length(Exports)]),
|
||||
do_patch_1(DbPool, Version, Mod, Exports)
|
||||
end,
|
||||
do_patch(DbPool, T, Maps);
|
||||
do_patch(_DbPool, [], _Maps) ->
|
||||
ok.
|
||||
|
||||
do_patch_1(DbPool, Version, Mod, [{Fun, Arity} | T]) ->
|
||||
try
|
||||
?INFO(?MODULE, "execute patch ~w:~w/~w", [Mod, Fun, Arity]),
|
||||
{Time, _} = timer:tc(erlang, apply, [Mod, Fun, [DbPool]]),
|
||||
?INFO(?MODULE, "execute patch ~w:~w/~w finished! elapsed time:~.2f/s", [Mod, Fun, Arity, Time / 1000 / 1000]),
|
||||
update_patch_version_rec(DbPool, Version, {Fun, Arity}),
|
||||
do_patch_1(DbPool, Version, Mod, T)
|
||||
catch
|
||||
Error:Reason:Trace ->
|
||||
?ERROR(?MODULE, "Error:~p Reason:~p~nTrace:~tp~n", [Trace, Error, Reason]),
|
||||
erlang:error(Reason)
|
||||
end;
|
||||
do_patch_1(_DbPool, _Version, _Mod, []) ->
|
||||
ok.
|
||||
|
||||
%% 更新版本记录
|
||||
update_patch_version_rec(DbPool, Version, Func) ->
|
||||
SelectSQL = io_lib:format("SELECT `func_list` FROM `~s` WHERE `version`= ?;", [?TABLE_PATCH_VERSION]),
|
||||
case mysql_poolboy:query(DbPool, SelectSQL, [Version]) of
|
||||
{ok, _Column, []} ->
|
||||
UpFuncList = [Func];
|
||||
{ok, _Column, [[FuncListStr]]} ->
|
||||
UpFuncList = string_to_term(FuncListStr) ++ [Func]
|
||||
end,
|
||||
ReplaceSQL = io_lib:format("REPLACE INTO `~s` (`version`,`func_list`) VALUES (?, ?);", [?TABLE_PATCH_VERSION]),
|
||||
ok = mysql_poolboy:query(DbPool, ReplaceSQL, [Version, term_to_string(UpFuncList)]).
|
||||
|
||||
string_to_term(Str) when is_binary(Str) ->
|
||||
string_to_term(unicode:characters_to_list(Str));
|
||||
string_to_term(Str) when is_list(Str) ->
|
||||
case erl_scan:string(Str ++ ".") of
|
||||
{ok, Tokens, _} ->
|
||||
case erl_parse:parse_term(Tokens) of
|
||||
{ok, Term} -> Term;
|
||||
_Err -> undefined
|
||||
end;
|
||||
Error ->
|
||||
throw({error, Error})
|
||||
end.
|
||||
|
||||
term_to_string(Term) ->
|
||||
lists:flatten(io_lib:format("~w", [Term])).
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
-module(ms_cache_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Helper macro for declaring children of supervisor
|
||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, infinity, Type, [I]}).
|
||||
|
||||
%% ===================================================================
|
||||
%% API functions
|
||||
%% ===================================================================
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
%% ===================================================================
|
||||
%% Supervisor callbacks
|
||||
%% ===================================================================
|
||||
|
||||
init([]) ->
|
||||
{
|
||||
ok,
|
||||
{
|
||||
{one_for_one, 5, 10},
|
||||
[
|
||||
%% ?CHILD(ms_cache_dao_gs_sup, supervisor),
|
||||
?CHILD(ms_cache_dao_sync_gs_sup, supervisor),
|
||||
?CHILD(ms_cache_dao_merge_gs_sup, supervisor),
|
||||
?CHILD(ms_cache_gs_sup, supervisor)
|
||||
]
|
||||
}
|
||||
}.
|
||||
@@ -0,0 +1,185 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 09. 1月 2025 21:18
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_tkv).
|
||||
%% API
|
||||
-export([
|
||||
init/0
|
||||
, init/1
|
||||
, is_init/1
|
||||
, get/2
|
||||
, get/3
|
||||
, add/2
|
||||
, add/3
|
||||
, update/2
|
||||
, update/3
|
||||
, replace/2
|
||||
, replace/3
|
||||
, delete/2
|
||||
, set_kv_list/2
|
||||
, get_key_list/1
|
||||
, get_value_list/1
|
||||
, get_kv_list/1
|
||||
, get_map/1
|
||||
, get_size/1
|
||||
, delete/1
|
||||
, is_key/2
|
||||
, foreach/2
|
||||
, fold/3
|
||||
]).
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
init(_) ->
|
||||
ok.
|
||||
|
||||
is_init(Type) ->
|
||||
erlang:get({?MODULE, Type}) /= undefined.
|
||||
|
||||
%% 添加一个record数据
|
||||
-spec(add(Type::atom(), Value::term()) -> term()).
|
||||
add(Type, Value) when is_tuple(Value) ->
|
||||
add(Type, erlang:element(2, Value), Value).
|
||||
|
||||
%% 添加数据
|
||||
-spec(add(Type :: atom(), Key :: term(), Value :: term()) -> term()).
|
||||
add(Type, Key, Value) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
erlang:put({?MODULE, Type}, #{Key => Value});
|
||||
MapValue ->
|
||||
erlang:put({?MODULE, Type}, maps:put(Key, Value, MapValue))
|
||||
end.
|
||||
|
||||
%% 获取某个值
|
||||
-spec(get(Type :: atom(), Key :: term()) -> term() | undefined).
|
||||
get(Type, Key) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
undefined;
|
||||
MapValue ->
|
||||
maps:get(Key, MapValue, undefined)
|
||||
end.
|
||||
%% 获取某个值
|
||||
-spec(get(Type :: atom(), Key :: term(), Default::term()) -> term() | Default::term()).
|
||||
get(Type, Key, Default) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
Default;
|
||||
MapValue ->
|
||||
maps:get(Key, MapValue, Default)
|
||||
end.
|
||||
|
||||
set_kv_list(Type, KVList) ->
|
||||
MapValue = maps:from_list(KVList),
|
||||
erlang:put({?MODULE, Type}, MapValue),
|
||||
ok.
|
||||
|
||||
get_kv_list(Type) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
[];
|
||||
MapValue ->
|
||||
maps:to_list(MapValue)
|
||||
end.
|
||||
|
||||
get_map(Type) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
#{};
|
||||
MapValue ->
|
||||
MapValue
|
||||
end.
|
||||
|
||||
get_value_list(Type) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
[];
|
||||
MapValue ->
|
||||
maps:values(MapValue)
|
||||
end.
|
||||
|
||||
get_key_list(Type) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
[];
|
||||
MapValue ->
|
||||
maps:keys(MapValue)
|
||||
end.
|
||||
|
||||
get_size(Type) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
0;
|
||||
MapValue ->
|
||||
maps:size(MapValue)
|
||||
end.
|
||||
|
||||
is_key(Type, Key) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
false;
|
||||
MapValue ->
|
||||
maps:is_key(Key, MapValue)
|
||||
end.
|
||||
|
||||
%% 删除元素
|
||||
-spec(delete(Type :: atom(), Key :: term()) -> term() | undefined).
|
||||
delete(Type, Key) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
ok;
|
||||
MapValue ->
|
||||
erlang:put({?MODULE, Type}, maps:remove(Key, MapValue))
|
||||
end.
|
||||
|
||||
delete(Type) ->
|
||||
erlang:erase({?MODULE, Type}).
|
||||
|
||||
update(Type, NewValue) when is_tuple(NewValue) ->
|
||||
update(Type, erlang:element(2, NewValue), NewValue).
|
||||
update(Type, Key, NewValue) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
error({update_type_not_exists, Type});
|
||||
MapValue ->
|
||||
case maps:is_key(Key, MapValue) of
|
||||
false ->
|
||||
error({update_key_not_exists, Type});
|
||||
true ->
|
||||
NewMap = maps:update(Key, NewValue, MapValue),
|
||||
erlang:put({?MODULE, Type}, NewMap)
|
||||
end
|
||||
end.
|
||||
|
||||
replace(Type, NewValue) when is_tuple(NewValue) ->
|
||||
replace(Type, erlang:element(2, NewValue), NewValue).
|
||||
replace(Type, Key, NewValue) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
add(Type, Key, NewValue);
|
||||
MapValue ->
|
||||
NewMap = maps:put(Key, NewValue, MapValue),
|
||||
erlang:put({?MODULE, Type}, NewMap)
|
||||
end.
|
||||
|
||||
foreach(Type, Fun) when is_function(Fun, 2) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
ok;
|
||||
MapValue ->
|
||||
maps:foreach(Fun, MapValue)
|
||||
end.
|
||||
|
||||
fold(Type, Fun, Init) when is_function(Fun, 3) ->
|
||||
case erlang:get({?MODULE, Type}) of
|
||||
undefined ->
|
||||
Init;
|
||||
MapValue ->
|
||||
maps:fold(Fun, Init, MapValue)
|
||||
end.
|
||||
@@ -0,0 +1,479 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, <COMPANY>
|
||||
%%% @doc
|
||||
%%% ms_cache组件模块内部方法实现
|
||||
%%% @end
|
||||
%%% Created : 2019-09-03 22:01:42
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_util).
|
||||
%%--------------------------------------------------------------------
|
||||
%% include
|
||||
%%--------------------------------------------------------------------
|
||||
-include("ms_cache.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% API exports
|
||||
%%--------------------------------------------------------------------
|
||||
-export([
|
||||
get_error_message/3
|
||||
, get_table_worker_count/1
|
||||
, get_table_worker_num/2
|
||||
, get_key_index/1
|
||||
, get_table_name/2
|
||||
, get_table_name/3
|
||||
, get_table_name_by_key/3
|
||||
, get_cache_db_mod/0
|
||||
, get_cache_opt_mod/1
|
||||
, get_data_node/0
|
||||
, get_all_table_workers/1
|
||||
, get_table_worker/1
|
||||
, get_table_worker/2
|
||||
, get_table_supervisor_name/1
|
||||
, get_table_worker_name/2
|
||||
, map_to_record/1
|
||||
, record_to_map/1
|
||||
, format_data/1
|
||||
, now_ms/0
|
||||
, wait_n_reply/3
|
||||
, wait_n_reply/4
|
||||
]).
|
||||
|
||||
-export([
|
||||
get_add_column_sql/2
|
||||
, get_del_column_sql/2
|
||||
, get_upd_column_sql/2
|
||||
, get_upd_column_sql/3
|
||||
]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API functions
|
||||
%%%===================================================================
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取错误信息
|
||||
%%
|
||||
%% @spec get_error_message(Tab :: atom(), Key :: integer() | binary() | tuple(), Reason :: list()) -> ok.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_error_message(Tab :: atom(), Key :: integer() | binary() | tuple(), Reason :: list()) -> ok.
|
||||
get_error_message(Tab, Key, Reason) ->
|
||||
lists:flatten(io_lib:format(Reason ++ ", " ++ atom_to_list(Tab) ++ "-~p", [Key])).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取主键的第一个索引值
|
||||
%%
|
||||
%% @spec get_key_index(Key :: integer() | binary() | tuple()) -> ok.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_key_index(Key :: integer() | binary() | tuple()) -> ok.
|
||||
get_key_index(Key) when is_tuple(Key) ->
|
||||
element(1, Key);
|
||||
get_key_index(Key) ->
|
||||
Key.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 根据表类型、主键/第一个索引值获取表名
|
||||
%%
|
||||
%% @spec get_table_name_by_key(Tab :: atom(), Type :: atom(), Key :: integer() | binary() | tuple()) -> ok.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_name_by_key(Table :: atom(), Type :: atom(), Key :: integer() | binary() | tuple()) -> TableName :: atom().
|
||||
get_table_name_by_key(Table, Type, Key) when is_tuple(Key) ->
|
||||
get_table_name_by_key(Table, Type, element(1, Key));
|
||||
get_table_name_by_key(Table, Type, Key) ->
|
||||
WorkerCount = get_table_worker_count(Table),
|
||||
WorkerNum = erlang:phash2(Key, WorkerCount) + 1,
|
||||
get_table_name(Table, Type, WorkerNum).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 根据类型获取缓存表名
|
||||
%%
|
||||
%% @spec get_table_name(Tab :: atom(), Type :: atom()) -> ok.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_name(Tab :: atom(), Type :: atom()) -> ok.
|
||||
get_table_name(Tab, Type) ->
|
||||
case ms_cache_config:get({ms_cache, table_name, {Tab, Type}}, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
TableName =
|
||||
case Type of
|
||||
idx ->
|
||||
Handler = ?TABLE_HANDLER(Tab),
|
||||
KeyList = Handler:key_list(),
|
||||
case length(KeyList) < 2 of
|
||||
true ->
|
||||
none;
|
||||
false ->
|
||||
list_to_atom("ms_cache_" ++ atom_to_list(Tab) ++ "_" ++ atom_to_list(idx))
|
||||
end;
|
||||
_ ->
|
||||
list_to_atom("ms_cache_" ++ atom_to_list(Tab) ++ "_" ++ atom_to_list(Type))
|
||||
end,
|
||||
ms_cache_config:put({ms_cache, table_name, {Tab, Type}}, TableName),
|
||||
TableName;
|
||||
TableName ->
|
||||
TableName
|
||||
end.
|
||||
|
||||
get_table_name(Tab, Type, WorkerNum) ->
|
||||
case ms_cache_config:get({ms_cache, table_name, {Tab, Type, WorkerNum}}, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
TableName =
|
||||
case Type of
|
||||
idx ->
|
||||
Handler = ?TABLE_HANDLER(Tab),
|
||||
KeyList = Handler:key_list(),
|
||||
case length(KeyList) < 2 of
|
||||
true ->
|
||||
none;
|
||||
false ->
|
||||
list_to_atom("ms_cache_" ++ atom_to_list(Tab) ++ "_" ++ atom_to_list(idx) ++ "_" ++ integer_to_list(WorkerNum))
|
||||
end;
|
||||
_ ->
|
||||
list_to_atom("ms_cache_" ++ atom_to_list(Tab) ++ "_" ++ atom_to_list(Type) ++ "_" ++ integer_to_list(WorkerNum))
|
||||
end,
|
||||
ms_cache_config:put({ms_cache, table_name, {Tab, Type, WorkerNum}}, TableName),
|
||||
TableName;
|
||||
TableName ->
|
||||
TableName
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 缓存数据库类型对应的模块 ets | mnesia
|
||||
%% ets类型 =》 ms_cache_edb
|
||||
%% mnesia类型 =》 ms_cache_mdb(效率问题已被弃用,可能后续部分功能并没有太好的支持,使用的话需要多测试)
|
||||
%%
|
||||
%%
|
||||
%% @spec get_cache_db_mod() -> ms_cache_edb | ms_cache_mdb.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_cache_db_mod() -> ms_cache_edb | ms_cache_mdb.
|
||||
get_cache_db_mod() -> ms_cache_edb.
|
||||
%% case application:get_env(game_server, is_develop, ?FALSE) of
|
||||
%% ?TRUE ->
|
||||
%% ms_cache_mdb;
|
||||
%% ?FALSE ->
|
||||
%% ms_cache_edb
|
||||
%% end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 缓存记录数据操作对应的模块
|
||||
%% 非安全模式使用ets数据表存储操作 => ms_cache_edb,效率高,容灾差
|
||||
%% 安全模式使用dets数据表存储操作 => ms_cache_ddb,效率低,容灾强
|
||||
%%
|
||||
%%
|
||||
%% @spec get_cache_opt_mod(IsSafeMode :: boolean()) -> ms_cache_edb | ms_cache_ddb.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_cache_opt_mod(IsSafeMode :: boolean()) -> ms_cache_edb | ms_cache_mdb.
|
||||
get_cache_opt_mod(?TRUE) -> ms_cache_ddb;
|
||||
get_cache_opt_mod(_) -> ms_cache_edb.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取缓存节点(存在缓存节点,其他节点经由缓存节点获取数据)
|
||||
%%
|
||||
%% @spec get_data_node() -> CacheNode :: atom().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_data_node() -> CacheNode :: atom().
|
||||
get_data_node() ->
|
||||
case ms_cache_config:get_env(data_node, undefined) of
|
||||
undefined ->
|
||||
erlang:node();
|
||||
CacheNode ->
|
||||
CacheNode
|
||||
end.
|
||||
|
||||
get_table_worker_count(Tab) ->
|
||||
Handler = ?TABLE_HANDLER(Tab),
|
||||
case Handler:is_pre_load() of
|
||||
true ->
|
||||
1;
|
||||
false ->
|
||||
ms_cache_config:get_env(table_worker_count, ?WORKER_COUNTER_DEFAULT)
|
||||
end.
|
||||
|
||||
get_table_worker_num(Tab, Key) when is_tuple(Key) ->
|
||||
case get_table_worker_count(Tab) of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
erlang:phash2(element(1, Key), WorkerCount) + 1;
|
||||
_ ->
|
||||
0
|
||||
end;
|
||||
get_table_worker_num(Tab, Key) ->
|
||||
case get_table_worker_count(Tab) of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
erlang:phash2(Key, WorkerCount) + 1;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取数据表对应的所有缓存进程
|
||||
%%
|
||||
%% @spec get_all_table_workers(Tab :: atom()) -> AllWorkers :: list().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_all_table_workers(Tab :: atom()) -> AllWorkers :: list().
|
||||
get_all_table_workers(Tab) ->
|
||||
case get_table_worker_count(Tab) of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
lists:foldl(
|
||||
fun(WorkerNum, Acc) ->
|
||||
SrvName = get_table_worker_name(Tab, WorkerNum),
|
||||
[SrvName | Acc]
|
||||
end, [], lists:seq(1, WorkerCount)
|
||||
);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 随机获取一个数据表对应的的缓存进程
|
||||
%%
|
||||
%% @spec get_table_worker(Tab :: atom()) -> Worker :: atom().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_worker(Tab :: atom()) -> Worker :: atom().
|
||||
get_table_worker(Tab) ->
|
||||
case get_table_worker_count(Tab)of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
WorkerNum = rand:uniform(WorkerCount),
|
||||
get_table_worker_name(Tab, WorkerNum);
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 根据主键获取数据表对应的的缓存进程
|
||||
%%
|
||||
%% @spec get_table_worker(Tab :: atom(), Key :: integer() | binary() | tuple()) -> Worker :: atom().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_worker(Tab :: atom(), Key :: integer() | binary() | tuple()) -> Worker :: atom().
|
||||
get_table_worker(Tab, Key) when is_tuple(Key) ->
|
||||
case get_table_worker_count(Tab) of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
WorkerNum = erlang:phash2(element(1, Key), WorkerCount) + 1,
|
||||
get_table_worker_name(Tab, WorkerNum);
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
get_table_worker(Tab, Key) ->
|
||||
case get_table_worker_count(Tab) of
|
||||
WorkerCount when WorkerCount > 0 ->
|
||||
WorkerNum = erlang:phash2(Key, WorkerCount) + 1,
|
||||
get_table_worker_name(Tab, WorkerNum);
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 根据序号获取缓存的工作进程名
|
||||
%%
|
||||
%% @spec get_table_maworker_name(Tab :: atom(), WorkerNum :: integer()) -> Worker :: atom().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_worker_name(Tab :: atom(), WorkerNum :: integer()) -> Worker :: atom().
|
||||
get_table_worker_name(Tab, WorkerNum) ->
|
||||
case ms_cache_config:get({ms_cache, table_worker_name, {Tab, WorkerNum}}, ?UNDEFINED) of
|
||||
?UNDEFINED ->
|
||||
WorkerName = list_to_atom("ms_cache_gs_" ++ atom_to_list(Tab) ++ "_" ++ integer_to_list(WorkerNum)),
|
||||
ms_cache_config:put({ms_cache, table_worker_name, {Tab, WorkerNum}}, WorkerName),
|
||||
WorkerName;
|
||||
WorkerName ->
|
||||
WorkerName
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 随机获取一个数据表缓存进程对应的监控进程名
|
||||
%%
|
||||
%% @spec get_table_supervisor_name(Tab :: atom()) -> Worker :: atom().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_table_supervisor_name(Tab :: atom()) -> Worker :: atom().
|
||||
get_table_supervisor_name(Tab) ->
|
||||
list_to_atom("ms_cache_gs_" ++ atom_to_list(Tab) ++ "_sup").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 缓存map数据转record
|
||||
%%
|
||||
%% @spec map_to_record(Map :: map() | tuple() | none) -> Record :: tuple() | none.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec map_to_record(Map :: map() | tuple() | none) -> Record :: tuple() | none.
|
||||
map_to_record(none) ->
|
||||
none;
|
||||
map_to_record(#{} = Map) ->
|
||||
Handler = ?TABLE_HANDLER(maps:get('_table', Map)),
|
||||
Handler:map_to_record(Map);
|
||||
map_to_record(Record) ->
|
||||
% Record.
|
||||
Tab = element(1, Record),
|
||||
Handler = ?TABLE_HANDLER(Tab),
|
||||
Handler:map_to_record(Record).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 缓存record数据转map
|
||||
%%
|
||||
%% @spec record_to_map(Record :: tuple() | map() | none) -> Map :: map() | none.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec record_to_map(Record :: tuple() | map() | none) -> Map :: map() | none.
|
||||
record_to_map(none) ->
|
||||
none;
|
||||
record_to_map(#{} = Map) ->
|
||||
% Map;
|
||||
Handler = ?TABLE_HANDLER(maps:get('_table', Map)),
|
||||
Handler:record_to_map(Map);
|
||||
record_to_map(Record) ->
|
||||
Tab = element(1, Record),
|
||||
Handler = ?TABLE_HANDLER(Tab),
|
||||
Handler:record_to_map(Record).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 转换数据格式为预定义格式
|
||||
%%
|
||||
%% @spec format_data(Data :: tuple() | map() | none) -> FmtData :: tuple() | map().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec format_data(Data :: tuple() | map() | none) -> FmtData :: tuple() | map() | none.
|
||||
format_data(none) ->
|
||||
none;
|
||||
format_data(Data) ->
|
||||
case ms_cache_config:get_env(data_fmt, record) of
|
||||
record ->
|
||||
map_to_record(Data);
|
||||
map ->
|
||||
record_to_map(Data)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取当前时间
|
||||
%%
|
||||
%% @spec now_ms() -> NowSec :: integer().
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec now_ms() -> NowSec :: integer().
|
||||
now_ms() ->
|
||||
erlang:system_time(milli_seconds).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 等待返回n次结果
|
||||
%%
|
||||
%% @spec wait_n_reply(SuccMsg :: any(), FailMsg :: any(), Count :: integer()) -> ok.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
wait_n_reply(_SuccMsg, _FailMsg, Count) when Count =< 0 ->
|
||||
ok;
|
||||
wait_n_reply(SuccMsg, FailMsg, Count) ->
|
||||
receive
|
||||
SuccMsg ->
|
||||
wait_n_reply(SuccMsg, FailMsg, Count-1);
|
||||
FailMsg ->
|
||||
throw(wait_n_reply_error)
|
||||
end.
|
||||
|
||||
wait_n_reply(_SuccMsg, _FailMsg, Count, _Timeout) when Count =< 0 ->
|
||||
ok;
|
||||
wait_n_reply(SuccMsg, FailMsg, Count, Timeout) ->
|
||||
receive
|
||||
SuccMsg ->
|
||||
wait_n_reply(SuccMsg, FailMsg, Count-1, Timeout);
|
||||
FailMsg ->
|
||||
throw(wait_n_reply_error)
|
||||
after Timeout ->
|
||||
throw(wait_n_reply_timeout)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取增加表字段的sql语句
|
||||
%%
|
||||
%% @spec get_add_column_sql(Tab :: atom(), ColumnInfo :: tuple()) -> {ok, SqlStr :: list()}.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_add_column_sql(Tab :: atom(), ColumnInfo :: tuple()) -> {ok, SqlStr :: list()}.
|
||||
get_add_column_sql(Table, ColumnInfo) ->
|
||||
ChangedSqlStr = column_changed_sql(ColumnInfo),
|
||||
SqlStr = io_lib:format("ALTER TABLE ~p ADD COLUMN ~s", [Table, ChangedSqlStr]),
|
||||
{?OK, SqlStr}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取删除表字段的sql语句
|
||||
%%
|
||||
%% @spec get_del_column_sql(Tab :: atom(), Column :: atom()) -> {ok, SqlStr :: list()}.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_del_column_sql(Tab :: atom(), Column :: atom()) -> {ok, SqlStr :: list()}.
|
||||
get_del_column_sql(Table, Column) ->
|
||||
SqlStr = io_lib:format("ALTER TABLE ~p drop COLUMN ~p", [Table, Column]),
|
||||
{?OK, SqlStr}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% 获取更新表字段的sql语句
|
||||
%%
|
||||
%% @spec get_upd_column_sql(Tab :: atom(), OldColumn :: atom(), ColumnInfo :: tuple()) -> {ok, SqlStr :: list()}.
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_upd_column_sql(Tab :: atom(), OldColumn :: atom(), ColumnInfo :: tuple()) -> {ok, SqlStr :: list()}.
|
||||
get_upd_column_sql(Table, OldColumn, ColumnInfo) ->
|
||||
ChangedSqlStr = column_changed_sql(ColumnInfo),
|
||||
SqlStr = io_lib:format("ALTER TABLE ~p CHANGE COLUMN ~p ~s", [Table, OldColumn, ChangedSqlStr]),
|
||||
{?OK, SqlStr}.
|
||||
get_upd_column_sql(Table, ColumnInfo) ->
|
||||
OldColumn = erlang:element(1, ColumnInfo),
|
||||
{?OK, SqlStr} = get_upd_column_sql(Table, OldColumn, ColumnInfo),
|
||||
{?OK, SqlStr}.
|
||||
|
||||
column_changed_sql({Column, int, Len, Content, ?TRUE}) when Len >= 20 ->
|
||||
io_lib:format("~p bigint(20) NOT NULL DEFAULT '0' COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, int, Len, Content, ?TRUE}) when Len =< 4 ->
|
||||
io_lib:format("~p tinyint(4) NOT NULL DEFAULT '0' COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, int, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p int(11) NOT NULL DEFAULT '0' COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, binary, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p varchar(~p) BINARY NOT NULL DEFAULT '' COMMENT '~s'", [Column, Len, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, binary, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p blob NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, atom, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p varchar(~p) BINARY NOT NULL DEFAULT 'undefined' COMMENT '~s'", [Column, erlang:min(Len, 255), unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, list, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p varchar(~p) BINARY NOT NULL DEFAULT '[]' COMMENT '~s'", [Column, Len, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, list, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p text NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, tuple, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p varchar(~p) BINARY NOT NULL DEFAULT '{}' COMMENT '~s'", [Column, Len, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, tuple, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p text NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, map, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p varchar(~p) BINARY NOT NULL DEFAULT '#{}' COMMENT '~s'", [Column, Len, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, map, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p text NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, any, Len, Content, ?TRUE}) when Len > 0 ->
|
||||
io_lib:format("~p mediumblob NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, any, _Len, Content, ?TRUE}) ->
|
||||
io_lib:format("~p blob NOT NULL COMMENT '~s'", [Column, unicode:characters_to_binary(Content)]);
|
||||
column_changed_sql({Column, decimal, {M, S}, Content, ?TRUE}) ->
|
||||
io_lib:format("~p decimal(~p, ~p) NOT NULL DEFAULT '0' COMMENT '~s'", [Column, M, S, unicode:characters_to_binary(Content)]).
|
||||
@@ -0,0 +1,911 @@
|
||||
%%%%%-------------------------------------------------------------------
|
||||
%%%%% @copyright (C) 2019, <COMPANY>
|
||||
%%%%% @doc
|
||||
%%%%% cache组件模块内部方法实现
|
||||
%%%%% @end
|
||||
%%%%% Created : 2019-09-03 22:01:42
|
||||
%%%%%-------------------------------------------------------------------
|
||||
-module(ms_cache_test).
|
||||
-include("base.hrl").
|
||||
%%
|
||||
%%%% ====================================================================
|
||||
%%%% Include files
|
||||
%%%% ====================================================================
|
||||
%%-include("../../include/ms_cache.hrl").
|
||||
%%-include("../../../game_server/include/auto_gen/cache/role_c.hrl").
|
||||
%%-include("../../../game_server/include/auto_gen/cache/item_c.hrl").
|
||||
%%
|
||||
%%-compile(export_all).
|
||||
%%-compile(nowarn_export_all).
|
||||
%%
|
||||
%%-define(CACHE_LIST, []).
|
||||
%%-define(PREPARE_SELECT_LIST, []).
|
||||
%%-define(PREPARE_UPDATE_LIST, []).
|
||||
%%
|
||||
%%
|
||||
%%%% 测试环境
|
||||
%%%% Intel(R) Core(TM)i5-8500 CPU @ 3.00GHz 3.00GHz, 16G内存, win10操作系统
|
||||
%%%% 测试结果
|
||||
%%%% 单key表,role表
|
||||
%%%% 1、插入100000笔数据,1000个进程插入,每个进程插入100笔数据 ms_cache_analyse:spawn_test_insert(role, 1000, 100).
|
||||
%%%% prepare for init
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=141000, one_time = 1.41, sec_num = 709219, timeout = undefined
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=125000, one_time = 1.25, sec_num = 800000, timeout = undefined
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=140000, one_time = 1.40, sec_num = 714285, timeout = undefined
|
||||
%%%% prepare for init sync
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=328000, one_time = 3.28, sec_num = 304878, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=109999, one_time = 1.09999, sec_num = 909099, timeout = undefined
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=93999, one_time = 0.93999, sec_num = 1063841, timeout = undefined
|
||||
%%%% {role,insert}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%
|
||||
%%
|
||||
%%%% 2、查询100000次数据,三次测试,
|
||||
%%%% 查询测试 timer:tc(fun() -> ms_cache:init(role, any), [ms_cache:lookup(role, N) || N<-lists:seq(1,100000)], ok end).
|
||||
%%%% 有缓存并发1000进程,每个进程查100次不同数据(prepare for init)
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=172000, one_time = 1.72, sec_num = 581395, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=172000, one_time = 1.72, sec_num = 581395, timeout = undefined
|
||||
%%%% 有缓存并发1000进程,每个进程查100次不同数据(prepare for execute)
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=141000, one_time = 1.41, sec_num = 709219, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=172000, one_time = 1.72, sec_num = 581395, timeout = undefined
|
||||
%%
|
||||
%%%% 无缓存并发1000进程,每个进程查100次不同数据(prepare for init)
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=15063000, one_time = 150.63, sec_num = 6638, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=17187000, one_time = 171.87, sec_num = 5818, timeout = undefined
|
||||
%%%% 无缓存并发1000进程,每个进程查100次不同数据(prepare for execute)
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=16313000, one_time = 163.13, sec_num = 6130, timeout = undefined
|
||||
%%%% {role,lookup}, finish_num:100000, cost_time=17749921, one_time = 177.49921, sec_num = 5633, timeout = undefined
|
||||
%%
|
||||
%%%% 3、更新100000笔数据,1000个进程,每个进程更新100笔数据
|
||||
%%%% prepare for init
|
||||
%%%% {role,update}, finish_num:100000, cost_time=109000, one_time = 1.09, sec_num = 917431, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=141000, one_time = 1.41, sec_num = 709219, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=94000, one_time = 0.94, sec_num = 1063829, timeout = undefined
|
||||
%%%% prepare for init sync
|
||||
%%%% {role,update}, finish_num:100000, cost_time=375000, one_time = 3.75, sec_num = 266666, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=313000, one_time = 3.13, sec_num = 319488, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=328000, one_time = 3.28, sec_num = 304878, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {role,update}, finish_num:100000, cost_time=109000, one_time = 1.09, sec_num = 917431, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=141000, one_time = 1.41, sec_num = 709219, timeout = undefined
|
||||
%%%% {role,update}, finish_num:100000, cost_time=139999, one_time = 1.39999, sec_num = 714290, timeout = undefined
|
||||
%%
|
||||
%%%% 4、删除100000笔数据,启动1000个进程,每个进程删除100笔数据
|
||||
%%%% prepare for init
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=47000, one_time = 0.47, sec_num = 2127659, timeout = undefined
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=32000, one_time = 0.32, sec_num = 3125000, timeout = undefined
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=62000, one_time = 0.62, sec_num = 1612903, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=63000, one_time = 0.63, sec_num = 1587301, timeout = undefined
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=62999, one_time = 0.62999, sec_num = 1587326, timeout = undefined
|
||||
%%%% {role,delete}, finish_num:100000, cost_time=46000, one_time = 0.46, sec_num = 2173913, timeout = undefined
|
||||
%%
|
||||
%%%% 双key表,item表
|
||||
%%%% 1、插入100000笔数据,1000个进程插入,每个进程插入100笔数据(第一个key相同),
|
||||
%%%% prepare for init
|
||||
%%%% {item,insert}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {item,insert}, finish_num:100000, cost_time=125000, one_time = 1.25, sec_num = 800000, timeout = undefined
|
||||
%%%% {item,insert}, finish_num:100000, cost_time=109000, one_time = 1.09, sec_num = 917431, timeout = undefined
|
||||
%%%% {item,insert}, finish_num:100000, cost_time=94000, one_time = 0.94, sec_num = 1063829, timeout = undefined
|
||||
%%
|
||||
%%%% 2、查询100000次数据,三次测试
|
||||
%%%% 有缓存prepare for init
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=172000, one_time = 1.72, sec_num = 581395, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=156000, one_time = 1.56, sec_num = 641025, timeout = undefined
|
||||
%%%% 有缓存prepare for execute
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=172000, one_time = 1.72, sec_num = 581395, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=219000, one_time = 2.19, sec_num = 456621, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=157000, one_time = 1.57, sec_num = 636942, timeout = undefined
|
||||
%%
|
||||
%%%% 无缓存prepare for init(首次从数据库加载)
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=16422000, one_time = 164.22, sec_num = 6089, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=16656000, one_time = 166.56, sec_num = 6003, timeout = undefined
|
||||
%%%% 无缓存prepare for execute(首次从数据库加载)
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=16422000, one_time = 164.22, sec_num = 6089, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=16656000, one_time = 166.56, sec_num = 6003, timeout = undefined
|
||||
%%%% {item,lookup}, finish_num:100000, cost_time=16984000, one_time = 169.84, sec_num = 5887, timeout = undefined
|
||||
%%
|
||||
%%%% 3、更新100000笔数据,1000个进程更新,每个进程更新100笔数据
|
||||
%%%% prepare for init
|
||||
%%%% {item,update}, finish_num:100000, cost_time=110000, one_time = 1.1, sec_num = 909090, timeout = undefined
|
||||
%%%% {item,update}, finish_num:100000, cost_time=93000, one_time = 0.93, sec_num = 1075268, timeout = undefined
|
||||
%%%% {item,update}, finish_num:100000, cost_time=125000, one_time = 1.25, sec_num = 800000, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {item,update}, finish_num:100000, cost_time=109000, one_time = 1.09, sec_num = 917431, timeout = undefined
|
||||
%%%% {item,update}, finish_num:100000, cost_time=78000, one_time = 0.78, sec_num = 1282051, timeout = undefined
|
||||
%%%% {item,update}, finish_num:100000, cost_time=125000, one_time = 1.25, sec_num = 800000, timeout = undefined
|
||||
%%
|
||||
%%
|
||||
%%%% 4、删除100000笔数据,1000个进程删除,每个进程删除100笔数据
|
||||
%%%% prepare for init
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=47000, one_time = 0.47, sec_num = 2127659, timeout = undefined
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=47000, one_time = 0.47, sec_num = 2127659, timeout = undefined
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=62000, one_time = 0.62, sec_num = 1612903, timeout = undefined
|
||||
%%%% prepare for execute
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=47000, one_time = 0.47, sec_num = 2127659, timeout = undefined
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=62000, one_time = 0.62, sec_num = 1612903, timeout = undefined
|
||||
%%%% {item,delete}, finish_num:100000, cost_time=63000, one_time = 0.63, sec_num = 1587301, timeout = undefined
|
||||
%%
|
||||
%%start() ->
|
||||
%% application:set_env(ms_cache, db_driver, 'mysql-otp'),
|
||||
%% application:set_env(ms_cache, db_user, "uegame"),
|
||||
%% application:set_env(ms_cache, db_pass, "uegame"),
|
||||
%% application:set_env(ms_cache, db_host, "172.16.11.210"),
|
||||
%% application:set_env(ms_cache, db_port, 3306),
|
||||
%% application:set_env(ms_cache, db_name, "ms_cache_test"),
|
||||
%% application:set_env(ms_cache, db_encoding, utf8),
|
||||
%% application:set_env(ms_cache, db_select_pool_name, select_pool_name),
|
||||
%% application:set_env(ms_cache, db_select_pool_size, 10),
|
||||
%% application:set_env(ms_cache, db_update_pool_name, update_pool_name),
|
||||
%% application:set_env(ms_cache, db_update_pool_size, 10),
|
||||
%% ms_cache:start(?CACHE_LIST, ?PREPARE_SELECT_LIST, ?PREPARE_UPDATE_LIST),
|
||||
%% ok.
|
||||
%%
|
||||
%%handle_result(Table, Tag, Num, Cnt, NowTime) ->
|
||||
%% erlang:spawn(
|
||||
%% fun() ->
|
||||
%% erlang:put(opt_name, {Table, Tag}),
|
||||
%% erlang:put(finish_cnt, {Num, 0}),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% {CostTime, Timeout} = timer:tc(fun() -> loop_result() end),
|
||||
%% OneTime = erlang:max(0.001, CostTime / (Num*Cnt)),
|
||||
%% SecNum = erlang:floor(1000000 / OneTime),
|
||||
%% ?ERROR(?MODULE, "~p, finish_num:~p, cost_time=~p, one_time = ~p, sec_num = ~p, timeout = ~p",
|
||||
%% [{Table, Tag}, Num*Cnt, CostTime, OneTime, SecNum, Timeout])
|
||||
%% end).
|
||||
%%
|
||||
%%loop_result() ->
|
||||
%% receive {finish, _N, TimeoutCnt} ->
|
||||
%% add_timeout(TimeoutCnt),
|
||||
%% case add_finish() of
|
||||
%% {Max, Now} when Now >= Max ->
|
||||
%% erlang:get(add_timeout);
|
||||
%% {_Max, _Now} ->
|
||||
%% loop_result()
|
||||
%% end
|
||||
%% after 3000 ->
|
||||
%% erlang:get(add_timeout)
|
||||
%% end.
|
||||
%%
|
||||
%%add_finish() ->
|
||||
%% {Max, Now} = erlang:get(finish_cnt),
|
||||
%% erlang:put(finish_cnt, {Max, Now+1}),
|
||||
%% {Max, Now+1}.
|
||||
%%
|
||||
%%%% 插入测试
|
||||
%%%% item : 1W进程,每个进程10次插入,耗时516毫秒 |
|
||||
%%spawn_test_insert(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, insert, Num, Cnt, NowTime),
|
||||
%%
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_insert(Table, (N-1)*Cnt+1, N*Cnt)
|
||||
%% end
|
||||
%% )
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%loop_insert(_, Min, Max) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_insert(role, Min, Max) ->
|
||||
%% FnUId = erlang:list_to_binary("client_" ++ integer_to_list(Min) ),
|
||||
%% ms_cache:insert(
|
||||
%% role,
|
||||
%% #role{
|
||||
%% role_id=Min,
|
||||
%% fn_game_id = <<"1629775983825100">>,
|
||||
%% fn_platform = <<"0">>,
|
||||
%% fn_uid = FnUId,
|
||||
%% fn_name = FnUId,
|
||||
%% role_name = erlang:integer_to_binary(Min rem 100)
|
||||
%% }
|
||||
%% ),
|
||||
%% loop_insert(role, Min+1, Max);
|
||||
%%loop_insert(item, Min, Max) ->
|
||||
%% ms_cache:insert(item, #item{ role_id=Max, item_id=Min }),
|
||||
%% loop_insert(item, Min+1, Max).
|
||||
%%
|
||||
%%%% 查询测试
|
||||
%%%% 无缓存无数据 1W进程, 每个进程10次查询,三次耗时 21109 | | 毫秒
|
||||
%%%% 有缓存无数据 10W进程,100W次查询,耗时94秒 <==> 45W超时 | 1W进程,10W次查询,耗时7秒 <==> 0超时(10W进程10W查询差不多 5秒)
|
||||
%%%% 有缓存有数据 1进程,每个进程10次查询,三次耗时 236 | 219 | 218 毫秒
|
||||
%%
|
||||
%%spawn_test_lookup(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, lookup, Num, Cnt, NowTime),
|
||||
%%
|
||||
%%%% ms_tool:fprof_pid(whereis(ms_cache_gs_item_1), 20000),
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_lookup(Table, (N-1)*Cnt+1, N*Cnt)
|
||||
%% end
|
||||
%% )
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%loop_lookup(_, Min, Max) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%%%% Cnt = erlang:get(opt_cnt),
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_lookup(role, Min, Max) ->
|
||||
%% try
|
||||
%% ms_cache:lookup(role, Min)
|
||||
%% catch _E:M:T ->
|
||||
%% case M of
|
||||
%% {timeout, _} ->
|
||||
%% add_timeout();
|
||||
%% {badmatch, _} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% ?ERROR(?MODULE, "M = ~p, Max = ~p, Trace = ~p", [M, Max, T])
|
||||
%% end
|
||||
%% end,
|
||||
%% loop_lookup(role, Min+1, Max);
|
||||
%%loop_lookup(item, Min, Max) ->
|
||||
%% try
|
||||
%% ms_cache:lookup(item, {Max, Min})
|
||||
%% catch _E:M:T ->
|
||||
%% case M of
|
||||
%% {timeout, _} ->
|
||||
%% add_timeout();
|
||||
%% {badmatch, _} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% ?ERROR(?MODULE, "M = ~p, Max = ~p, Trace = ~p", [M, Max, T])
|
||||
%% end
|
||||
%% end,
|
||||
%% loop_lookup(item, Min+1, Max).
|
||||
%%
|
||||
%%%% 更新测试
|
||||
%%%% 10W进程,100W次更新,耗时 秒 <==> 0超时 | 1W进程,10W次更新,耗时1秒 <==> 10秒内查询超时
|
||||
%%spawn_test_update_element(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, update_element, Num, Cnt, NowTime),
|
||||
%%
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_update_element(Table, (N-1)*Cnt+1, N*Cnt, NowTime)
|
||||
%% end)
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%
|
||||
%%loop_update_element(_, Min, Max, _NowTime) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_update_element(role, Min, Max, NowTime) ->
|
||||
%% ms_cache:update_element(role, Min, {#role.exp, NowTime+Min}),
|
||||
%% loop_update_element(role, Min+1, Max, NowTime);
|
||||
%%loop_update_element(item, Min, Max, NowTime) ->
|
||||
%% ms_cache:update_element(item, {Max, Min}, {#item.number, NowTime+Min }),
|
||||
%% loop_update_element(item, Min+1, Max, NowTime).
|
||||
%%
|
||||
%%%% 更新测试
|
||||
%%%% 10W进程,100W次更新,耗时 秒 <==> 0超时 | 1W进程,10W次更新,耗时1秒 <==> 10秒内查询超时
|
||||
%%spawn_test_update(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, update, Num, Cnt, NowTime),
|
||||
%%
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_update(Table, (N-1)*Cnt+1, N*Cnt, NowTime)
|
||||
%% end)
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%
|
||||
%%loop_update(_, Min, Max, _NowTime) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_update(role, Min, Max, NowTime) ->
|
||||
%% ms_cache:update(role, #role{ role_id=Min,exp=NowTime+Min }),
|
||||
%% loop_update(role, Min+1, Max, NowTime);
|
||||
%%loop_update(item, Min, Max, NowTime) ->
|
||||
%% ms_cache:update(item, #item{ role_id=Max, item_id=Min,number=NowTime+Min }),
|
||||
%% loop_update(item, Min+1, Max, NowTime).
|
||||
%%
|
||||
%%%% 删除测试
|
||||
%%%% 10W进程,100W次删除,耗时4秒 <==> 0超时 | 1W进程,10W次删除,耗时0秒 <==> 10内超时
|
||||
%%spawn_test_delete(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, delete, Num, Cnt, NowTime),
|
||||
%%
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_delete(Table, (N-1)*Cnt+1, N*Cnt)
|
||||
%% end)
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%
|
||||
%%loop_delete(_, Min, Max) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_delete(role, Min, Max) ->
|
||||
%% ms_cache:delete(role, Min),
|
||||
%% loop_delete(role, Min+1, Max);
|
||||
%%loop_delete(item, Min, Max) ->
|
||||
%% ms_cache:delete(item, {Max, Min}),
|
||||
%% loop_delete(item, Min+1, Max).
|
||||
%%
|
||||
%%
|
||||
%%% test_lookup_by_key_index(Min, Max) ->
|
||||
%%% LookupCostTime = timer:tc(?MODULE, loop_lookup_by_key_index, [Min, Max, {0,0}]),
|
||||
%%% ?ERROR(?MODULE, "lookup_by_key_index_num:~p cost_time=~p", [Max-Min+1, LookupCostTime]).
|
||||
%%spawn_test_lookup_by_key_index(Table, Num, Cnt) ->
|
||||
%% NowTime = now_ms(),
|
||||
%% Pid = handle_result(Table, lookup_by_key_index, Num, Cnt, NowTime),
|
||||
%%
|
||||
%% F = fun(N) ->
|
||||
%% spawn(
|
||||
%% fun() ->
|
||||
%% ms_cache:init(Table, any),
|
||||
%% erlang:put(finish_pid, Pid),
|
||||
%% erlang:put(max_num, N*Cnt),
|
||||
%% erlang:put(start_time, NowTime),
|
||||
%% erlang:put(opt_cnt, Cnt),
|
||||
%% erlang:put(opt_seq, N),
|
||||
%% loop_lookup_by_key_index(Table, (N-1)*Cnt+1, N*Cnt)
|
||||
%% end)
|
||||
%% end,
|
||||
%% lists:foreach(F, lists:seq(1,Num)).
|
||||
%%
|
||||
%%loop_lookup_by_key_index(_, Min, Max) when Min =< 0;Max =< 0; Max<Min ->
|
||||
%% TimeoutCnt = erlang:get(add_timeout),
|
||||
%% ?IF(Max == erlang:get(max_num), erlang:get(finish_pid) ! {finish, erlang:get(opt_seq), TimeoutCnt});
|
||||
%%loop_lookup_by_key_index(item, Min, Max) ->
|
||||
%% try
|
||||
%% ms_cache:lookup_by_key_index(item, Max)
|
||||
%% catch _E:M:T ->
|
||||
%% case M of
|
||||
%% {timeout, _} ->
|
||||
%% add_timeout();
|
||||
%% {badmatch, _} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% ?ERROR(?MODULE, "M = ~p, Max = ~p, Trace = ~p", [M, Max, T])
|
||||
%% end
|
||||
%% end,
|
||||
%% loop_lookup_by_key_index(item, Min+1, Max);
|
||||
%%loop_lookup_by_key_index(role, Min, Max) ->
|
||||
%% try
|
||||
%% ms_cache:lookup_by_key_index(role, Max)
|
||||
%% catch _E:M:T ->
|
||||
%% case M of
|
||||
%% {timeout, _} ->
|
||||
%% add_timeout();
|
||||
%% {badmatch, _} ->
|
||||
%% ok;
|
||||
%% _ ->
|
||||
%% ?ERROR(?MODULE, "M = ~p, Max = ~p, Trace = ~p", [M, Max, T])
|
||||
%% end
|
||||
%% end,
|
||||
%% loop_lookup_by_key_index(role, Min+1, Max).
|
||||
%%
|
||||
%%add_timeout() ->
|
||||
%% add_timeout(1).
|
||||
%%add_timeout(undefined) ->
|
||||
%% ok;
|
||||
%%add_timeout(Add) ->
|
||||
%% case erlang:get(add_timeout) of
|
||||
%% undefined ->
|
||||
%% erlang:put(add_timeout, Add);
|
||||
%% N ->
|
||||
%% erlang:put(add_timeout, N+Add)
|
||||
%% end.
|
||||
%%
|
||||
%%
|
||||
%%%% @doc 当前Unix时间戳(秒)
|
||||
%%now_sec() ->
|
||||
%% erlang:system_time(second).
|
||||
%%
|
||||
%%%% @doc 当前Unix时间戳(毫秒)
|
||||
%%now_ms() ->
|
||||
%% erlang:system_time(millisecond).
|
||||
%%
|
||||
%%
|
||||
%%
|
||||
%%%% ===================== 插入性能对比 =====================
|
||||
%%%%test_insert_ets , Num = 1000, CostTime = 512, QPS = 1953125
|
||||
%%%%test_insert_ets , Num = 1000, CostTime = 409, QPS = 2444987
|
||||
%%%%test_insert_ets , Num = 1000, CostTime = 819, QPS = 1221001
|
||||
%%
|
||||
%%%%test_insert_mnesia, Num = 1000, CostTime = 1536, QPS = 651041
|
||||
%%%%test_insert_mnesia, Num = 1000, CostTime = 1536, QPS = 651041
|
||||
%%%%test_insert_mnesia, Num = 1000, CostTime = 2560, QPS = 390625
|
||||
%%
|
||||
%%%%test_insert_dets , Num = 1000, CostTime = 56217, QPS = 17788
|
||||
%%%%test_insert_dets , Num = 1000, CostTime = 23859, QPS = 41912
|
||||
%%%%test_insert_dets , Num = 1000, CostTime = 24371, QPS = 41032
|
||||
%%
|
||||
%%%%test_insert_mysql , Num = 1000, CostTime = 2159922, QPS = 462
|
||||
%%%%test_insert_mysql , Num = 1000, CostTime = 2528050, QPS = 395
|
||||
%%%%test_insert_mysql , Num = 1000, CostTime = 2245733, QPS = 445
|
||||
%%%% ===================== 查询性能对比 =====================
|
||||
%%%%test_lookup_ets , Num = 1000, CostTime = 307, QPS = 3257328
|
||||
%%%%test_lookup_ets , Num = 1000, CostTime = 307, QPS = 3257328
|
||||
%%%%test_lookup_ets , Num = 1000, CostTime = 614, QPS = 1628664
|
||||
%%
|
||||
%%%%test_lookup_mnesia, Num = 1000, CostTime = 512, QPS = 1953125
|
||||
%%%%test_lookup_mnesia, Num = 1000, CostTime = 409, QPS = 2444987
|
||||
%%%%test_lookup_mnesia, Num = 1000, CostTime = 512, QPS = 1953125
|
||||
%%
|
||||
%%%%test_lookup_dets , Num = 1000, CostTime = 9420, QPS = 106157
|
||||
%%%%test_lookup_dets , Num = 1000, CostTime = 8908, QPS = 112258
|
||||
%%%%test_lookup_dets , Num = 1000, CostTime = 8499, QPS = 117660
|
||||
%%
|
||||
%%%%test_lookup_mysql , Num = 1000, CostTime = 817356, QPS = 1223
|
||||
%%%%test_lookup_mysql , Num = 1000, CostTime = 834764, QPS = 1197
|
||||
%%%%test_lookup_mysql , Num = 1000, CostTime = 888319, QPS = 1125
|
||||
%%%% ===================== 更新性能对比 =====================
|
||||
%%%%test_update_ets , Num = 1000, CostTime = 307, QPS = 3257328
|
||||
%%%%test_update_ets , Num = 1000, CostTime = 307, QPS = 3257328
|
||||
%%%%test_update_ets , Num = 1000, CostTime = 512, QPS = 1953125
|
||||
%%
|
||||
%%%%test_update_mnesia, Num = 1000, CostTime = 1331, QPS = 751314
|
||||
%%%%test_update_mnesia, Num = 1000, CostTime = 1433, QPS = 697836
|
||||
%%%%test_update_mnesia, Num = 1000, CostTime = 1536, QPS = 651041
|
||||
%%
|
||||
%%%%test_update_dets , Num = 1000, CostTime = 11571, QPS = 86422
|
||||
%%%%test_update_dets , Num = 1000, CostTime = 11878, QPS = 84189
|
||||
%%%%test_update_dets , Num = 1000, CostTime = 11264, QPS = 88778
|
||||
%%
|
||||
%%%%test_update_mysql , Num = 1000, CostTime = 2192383, QPS = 456
|
||||
%%%%test_update_mysql , Num = 1000, CostTime = 2403429, QPS = 416
|
||||
%%%%test_update_mysql , Num = 1000, CostTime = 2677656, QPS = 373
|
||||
%%%% ===================== 删除性能对比 =====================
|
||||
%%%%test_delete_ets , Num = 1000, CostTime = 102, QPS = 3257328
|
||||
%%%%test_delete_ets , Num = 1000, CostTime = 102, QPS = 3257328
|
||||
%%%%test_delete_ets , Num = 1000, CostTime = 204, QPS = 1628664
|
||||
%%
|
||||
%%%%test_delete_mnesia, Num = 1000, CostTime = 819, QPS = 1953125
|
||||
%%%%test_delete_mnesia, Num = 1000, CostTime = 1024, QPS = 2444987
|
||||
%%%%test_delete_mnesia, Num = 1000, CostTime = 921, QPS = 1953125
|
||||
%%
|
||||
%%%%test_delete_dets , Num = 1000, CostTime = 14950, QPS = 106157
|
||||
%%%%test_delete_dets , Num = 1000, CostTime = 15052, QPS = 112258
|
||||
%%%%test_delete_dets , Num = 1000, CostTime = 14643, QPS = 117660
|
||||
%%
|
||||
%%%%test_delete_mysql , Num = 1000, CostTime = 1655295, QPS = 1223
|
||||
%%%%test_delete_mysql , Num = 1000, CostTime = 2399231, QPS = 1197
|
||||
%%%%test_delete_mysql , Num = 1000, CostTime = 1866956, QPS = 1125
|
||||
%%
|
||||
%%test_init() ->
|
||||
%% filelib:ensure_dir("dets/*"),
|
||||
%% {?OK, Options} = ms_cache_gs:merge_options([], role_c),
|
||||
%% ms_cache_edb:create_table(e_test_role, Options),
|
||||
%% ms_cache_edb2:create_table(m_test_role, [{type, set}, {attributes, role_c:field_list()}, {record_name, role}, {ram_copies, [erlang:node()]}]),
|
||||
%% ms_cache_ddb:create_table(d_test_role, []),
|
||||
%% ok.
|
||||
%%
|
||||
%%test_insert(Num) ->
|
||||
%% lists:foreach(
|
||||
%% fun({Mod, Table}) ->
|
||||
%% NowTime11 = now_sec(),
|
||||
%% {Time11, ?OK} = timer:tc(fun() -> test_insert_all(NowTime11, Num, Mod, Table) end),
|
||||
%% NowTime12 = now_sec(),
|
||||
%% {Time12, ?OK} = timer:tc(fun() -> test_update_all(NowTime12, Num, Mod, Table) end),
|
||||
%% {Time13, ?OK} = timer:tc(fun() -> test_lookup_all(Num, Mod, Table) end),
|
||||
%% {Time14, ?OK} = timer:tc(fun() -> test_delete_all(Num, Mod, Table) end),
|
||||
%%
|
||||
%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%% ?ERROR(?MODULE, "~p insert , Num = ~p, CostTime = ~p, QPS = ~p", [Table, Num, Time11, erlang:trunc(1000000/(Time11/Num))]),
|
||||
%% ?ERROR(?MODULE, "~p lookup , Num = ~p, CostTime = ~p, QPS = ~p", [Table, Num, Time13, erlang:trunc(1000000/(Time13/Num))]),
|
||||
%% ?ERROR(?MODULE, "~p update , Num = ~p, CostTime = ~p, QPS = ~p", [Table, Num, Time12, erlang:trunc(1000000/(Time12/Num))]),
|
||||
%% ?ERROR(?MODULE, "~p delete , Num = ~p, CostTime = ~p, QPS = ~p", [Table, Num, Time14, erlang:trunc(1000000/(Time13/Num))]),
|
||||
%%
|
||||
%% erlang:garbage_collect()
|
||||
%% end, [{ets, e_test_role}, {ms_cache_mdb, m_test_role}, {ms_cache_ddb, d_test_role}]
|
||||
%% ),
|
||||
%%
|
||||
%% NowTime31 = now_sec(),
|
||||
%% {Time31, ?OK} = timer:tc(fun() -> test_insert_mysql(NowTime31, Num) end),
|
||||
%% NowTime32 = now_sec(),
|
||||
%% {Time32, ?OK} = timer:tc(fun() -> test_update_mysql(NowTime32, Num) end),
|
||||
%% {Time33, ?OK} = timer:tc(fun() -> test_lookup_mysql(Num) end),
|
||||
%% {Time34, ?OK} = timer:tc(fun() -> test_delete_mysql(Num) end),
|
||||
%%
|
||||
%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%% ?ERROR(?MODULE, "test_insert_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time31, erlang:trunc(1000000/(Time31/Num))]),
|
||||
%% ?ERROR(?MODULE, "test_lookup_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time33, erlang:trunc(1000000/(Time33/Num))]),
|
||||
%% ?ERROR(?MODULE, "test_update_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time32, erlang:trunc(1000000/(Time32/Num))]),
|
||||
%% ?ERROR(?MODULE, "test_delete_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time34, erlang:trunc(1000000/(Time33/Num))]),
|
||||
%%
|
||||
%%
|
||||
%%%% NowTime11 = now_sec(),
|
||||
%%%% {Time11, ?OK} = timer:tc(fun() -> test_insert_ets(NowTime11, Num) end),
|
||||
%%%% NowTime12 = now_sec(),
|
||||
%%%% {Time12, ?OK} = timer:tc(fun() -> test_update_ets(NowTime12, Num) end),
|
||||
%%%% {Time13, ?OK} = timer:tc(fun() -> test_lookup_ets(Num) end),
|
||||
%%%% {Time14, ?OK} = timer:tc(fun() -> test_delete_ets(Num) end),
|
||||
%%%% erlang:garbage_collect(),
|
||||
%%%%
|
||||
%%%% NowTime41 = now_sec(),
|
||||
%%%% {Time41, ?OK} = timer:tc(fun() -> test_insert_mnesia(NowTime41, Num) end),
|
||||
%%%% NowTime42 = now_sec(),
|
||||
%%%% {Time42, ?OK} = timer:tc(fun() -> test_update_mnesia(NowTime42, Num) end),
|
||||
%%%% {Time43, ?OK} = timer:tc(fun() -> test_lookup_mnesia(Num) end),
|
||||
%%%% {Time44, ?OK} = timer:tc(fun() -> test_delete_mnesia(Num) end),
|
||||
%%%% erlang:garbage_collect(),
|
||||
%%%%
|
||||
%%%% NowTime21 = now_sec(),
|
||||
%%%% {Time21, ?OK} = timer:tc(fun() -> test_insert_dets(NowTime21, Num) end),
|
||||
%%%% NowTime22 = now_sec(),
|
||||
%%%% {Time22, ?OK} = timer:tc(fun() -> test_update_dets(NowTime22, Num) end),
|
||||
%%%% {Time23, ?OK} = timer:tc(fun() -> test_lookup_dets(Num) end),
|
||||
%%%% {Time24, ?OK} = timer:tc(fun() -> test_delete_dets(Num) end),
|
||||
%%%% erlang:garbage_collect(),
|
||||
%%
|
||||
%%%% NowTime31 = now_sec(),
|
||||
%%%% {Time31, ?OK} = timer:tc(fun() -> test_insert_mysql(NowTime31, Num) end),
|
||||
%%%% NowTime32 = now_sec(),
|
||||
%%%% {Time32, ?OK} = timer:tc(fun() -> test_update_mysql(NowTime32, Num) end),
|
||||
%%%% {Time33, ?OK} = timer:tc(fun() -> test_lookup_mysql(Num) end),
|
||||
%%%% {Time34, ?OK} = timer:tc(fun() -> test_delete_mysql(Num) end),
|
||||
%%%% erlang:garbage_collect(),
|
||||
%%%%
|
||||
%%%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%%%% ?ERROR(?MODULE, "test_insert_ets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time11, erlang:trunc(1000000/(Time11/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_insert_mnesia, Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time41, erlang:trunc(1000000/(Time41/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_insert_dets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time21, erlang:trunc(1000000/(Time21/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_insert_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time31, erlang:trunc(1000000/(Time31/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%%%% ?ERROR(?MODULE, "test_lookup_ets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time13, erlang:trunc(1000000/(Time13/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_lookup_mnesia, Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time43, erlang:trunc(1000000/(Time43/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_lookup_dets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time23, erlang:trunc(1000000/(Time23/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_lookup_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time33, erlang:trunc(1000000/(Time33/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%%%% ?ERROR(?MODULE, "test_update_ets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time12, erlang:trunc(1000000/(Time12/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_update_mnesia, Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time42, erlang:trunc(1000000/(Time42/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_update_dets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time22, erlang:trunc(1000000/(Time22/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_update_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time32, erlang:trunc(1000000/(Time32/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "==========================================================================================================", []),
|
||||
%%%% ?ERROR(?MODULE, "test_delete_ets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time14, erlang:trunc(1000000/(Time13/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_delete_mnesia, Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time44, erlang:trunc(1000000/(Time43/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_delete_dets , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time24, erlang:trunc(1000000/(Time23/Num))]),
|
||||
%%%% ?ERROR(?MODULE, "test_delete_mysql , Num = ~p, CostTime = ~p, QPS = ~p", [Num, Time34, erlang:trunc(1000000/(Time33/Num))]),
|
||||
%%
|
||||
%% ets:delete_all_objects(e_test_role),
|
||||
%% ms_cache_edb2:delete_all_objects(m_test_role),
|
||||
%% ms_cache_ddb:delete_all_objects(d_test_role),
|
||||
%% role_c:delete_all(?UPDATE_POOL),
|
||||
%%
|
||||
%% erlang:garbage_collect(),
|
||||
%%
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_insert_all(_NowSec, Num, _Mod, _Table) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_insert_all(NowSec, Num, Mod, Table) ->
|
||||
%% Mod:insert(Table, new_role(Num, NowSec)),
|
||||
%% test_insert_all(NowSec, Num-1, Mod, Table).
|
||||
%%
|
||||
%%test_update_all(_NowSec, Num, _Mod, _Table) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_update_all(NowSec, Num, Mod, Table) ->
|
||||
%% Mod:insert(Table, new_role(Num, NowSec)),
|
||||
%% test_update_all(NowSec, Num-1, Mod, Table).
|
||||
%%
|
||||
%%test_lookup_all(Num, _Mod, _Table) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_lookup_all(Num, Mod, Table) ->
|
||||
%% Mod:lookup(Table, Num),
|
||||
%% test_lookup_all(Num-1, Mod, Table),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_delete_all(Num, _Mod, _Table) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_delete_all(Num, Mod, Table) ->
|
||||
%% Mod:delete(Table, Num),
|
||||
%% test_delete_all(Num-1, Mod, Table),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_insert_ets(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_insert_ets(NowSec, Num) ->
|
||||
%% ets:insert(
|
||||
%% e_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_insert_ets(NowSec, Num-1).
|
||||
%%
|
||||
%%test_update_ets(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_update_ets(NowSec, Num) ->
|
||||
%% ets:insert(
|
||||
%% e_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_update_ets(NowSec, Num-1).
|
||||
%%
|
||||
%%test_lookup_ets(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_lookup_ets(Num) ->
|
||||
%% ets:lookup(e_test_role, Num),
|
||||
%% test_lookup_ets(Num-1),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_delete_ets(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_delete_ets(Num) ->
|
||||
%% ets:delete(e_test_role, Num),
|
||||
%% test_delete_ets(Num-1),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%
|
||||
%%test_insert_mnesia(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_insert_mnesia(NowSec, Num) ->
|
||||
%% ms_cache_edb2:insert(
|
||||
%% m_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_insert_mnesia(NowSec, Num-1).
|
||||
%%
|
||||
%%test_update_mnesia(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_update_mnesia(NowSec, Num) ->
|
||||
%% ms_cache_edb2:insert(
|
||||
%% m_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_insert_mnesia(NowSec, Num-1).
|
||||
%%
|
||||
%%test_lookup_mnesia(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_lookup_mnesia(Num) ->
|
||||
%% ms_cache_edb2:lookup(m_test_role, Num),
|
||||
%% test_lookup_mnesia(Num-1),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_delete_mnesia(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_delete_mnesia(Num) ->
|
||||
%% ms_cache_edb2:delete(m_test_role, Num),
|
||||
%% test_delete_mnesia(Num-1),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_insert_dets(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_insert_dets(NowSec, Num) ->
|
||||
%% ms_cache_ddb:insert(
|
||||
%% d_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_insert_dets(NowSec, Num-1).
|
||||
%%
|
||||
%%test_update_dets(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_update_dets(NowSec, Num) ->
|
||||
%% ms_cache_ddb:insert(
|
||||
%% d_test_role,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_update_dets(NowSec, Num-1).
|
||||
%%
|
||||
%%test_lookup_dets(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_lookup_dets(Num) ->
|
||||
%% ms_cache_ddb:lookup(d_test_role, Num),
|
||||
%% test_lookup_dets(Num - 1).
|
||||
%%
|
||||
%%test_delete_dets(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_delete_dets(Num) ->
|
||||
%% ms_cache_ddb:delete(d_test_role, Num),
|
||||
%% test_delete_dets(Num - 1).
|
||||
%%
|
||||
%%test_insert_mysql(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_insert_mysql(NowSec, Num) ->
|
||||
%% role_c:insert(
|
||||
%% ?UPDATE_POOL,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_insert_mysql(NowSec, Num-1).
|
||||
%%
|
||||
%%test_update_mysql(_NowSec, Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_update_mysql(NowSec, Num) ->
|
||||
%% role_c:update(
|
||||
%% ?UPDATE_POOL,
|
||||
%% new_role(Num, NowSec)
|
||||
%% ),
|
||||
%% test_update_mysql(NowSec, Num-1).
|
||||
%%
|
||||
%%test_lookup_mysql(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_lookup_mysql(Num) ->
|
||||
%% role_c:select_by_key(?SELECT_POOL, Num),
|
||||
%% test_lookup_mysql(Num - 1).
|
||||
%%
|
||||
%%test_delete_mysql(Num) when Num =< 0 ->
|
||||
%% ?OK;
|
||||
%%test_delete_mysql(Num) ->
|
||||
%% role_c:delete(?UPDATE_POOL, Num),
|
||||
%% test_delete_mysql(Num - 1).
|
||||
%%
|
||||
%%new_role(Num, NowSec) ->
|
||||
%% #role{
|
||||
%% '_id' = Num,
|
||||
%% role_id=Num,
|
||||
%% fn_game_id = <<"1629775983825100">>,
|
||||
%% fn_platform = <<"0">>,
|
||||
%% fn_uid = <<"test">>,
|
||||
%% fn_name = <<"test">>,
|
||||
%% role_name = erlang:integer_to_binary(Num rem 255),
|
||||
%% last_login_time = NowSec,
|
||||
%% last_logout_time = NowSec
|
||||
%% }.
|
||||
%%
|
||||
%%test_ets_index(Max, IndexNum) ->
|
||||
%% %% 删除测试的内存表
|
||||
%% catch ms_cache_edb2:close(ets_test_bag),
|
||||
%% catch ms_cache_edb3:close(ets_test_set),
|
||||
%% catch ms_cache_mdb:close(mnesia_test),
|
||||
%% catch ms_cache_edb:close(ets_test_ordered_set),
|
||||
%%
|
||||
%% %% 创建测试的内存表
|
||||
%% {?OK, Options} = ms_cache_gs:merge_options([], role_c),
|
||||
%% ms_cache_edb2:create_table(ets_test_bag, Options),
|
||||
%% ms_cache_edb3:create_table(ets_test_set, Options),
|
||||
%% ms_cache_mdb:create_table(mnesia_test, Options),
|
||||
%% ms_cache_edb:create_table(ets_test_ordered_set, Options),
|
||||
%% %% 查询玩家数据
|
||||
%% Now = now_sec(),
|
||||
%% Roles = [new_role(RoleId, Now) || RoleId <- lists:seq(1, Max)],
|
||||
%%
|
||||
%% %% insert
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:insert_new(ets_test_bag, Role) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:insert_new(ets_test_set, Role) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:insert_new(mnesia_test, Role) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:insert_new(ets_test_ordered_set, Role) end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag insert Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag, TimeSet, TimeMnesia, TimeEtsOrder]),
|
||||
%%
|
||||
%% %% update
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag0, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:insert(ets_test_bag, Role#role{ last_login_time = Role#role.last_login_time+1 }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet0, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:insert(ets_test_set, Role#role{ last_login_time = Role#role.last_login_time+1 }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia0, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:insert(mnesia_test, Role#role{ last_login_time = Role#role.last_login_time+1 }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder0, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:insert(ets_test_ordered_set, Role#role{ last_login_time = Role#role.last_login_time+1 }) end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag update Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag0, TimeSet0, TimeMnesia0, TimeEtsOrder0]),
|
||||
%%
|
||||
%% %% update index
|
||||
%% Rem = ?IF(IndexNum > Max, Max, Max div IndexNum),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag4, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:insert(ets_test_bag, Role#role{ role_name = erlang:integer_to_binary(Role#role.role_id rem Rem) }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet4, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:insert(ets_test_set, Role#role{ role_name = erlang:integer_to_binary(Role#role.role_id rem Rem) }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia4, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:insert(mnesia_test, Role#role{ role_name = erlang:integer_to_binary(Role#role.role_id rem Rem) }) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder4, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:insert(ets_test_ordered_set, Role#role{ role_name = erlang:integer_to_binary(Role#role.role_id rem Rem) }) end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag update_index Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag4, TimeSet4, TimeMnesia4, TimeEtsOrder4]),
|
||||
%%
|
||||
%% %% lookup
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag2, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:lookup(ets_test_bag, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet2, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:lookup(ets_test_set, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia2, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:lookup(mnesia_test, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder2, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:lookup(ets_test_ordered_set, Role#role.'_id') end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag lookup Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag2, TimeSet2, TimeMnesia2, TimeEtsOrder2]),
|
||||
%%
|
||||
%% %% lookup_by_key_index
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag3, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:index_read(ets_test_bag, Role#role.role_id, #role.role_id) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet3, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:index_read(ets_test_set, Role#role.role_id, #role.role_id) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia3, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:index_read(mnesia_test, Role#role.role_id, #role.role_id) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder3, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:index_read(ets_test_ordered_set, Role#role.role_id, #role.role_id) end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag lookup_by_key_index Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag3, TimeSet3, TimeMnesia3, TimeEtsOrder3]),
|
||||
%%
|
||||
%% %% index_read
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag5, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:index_read(ets_test_bag, Role#role.role_name, #role.role_name) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet5, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:index_read(ets_test_set, Role#role.role_name, #role.role_name) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia5, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:index_read(mnesia_test, Role#role.role_name, #role.role_name) end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder5, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:index_read(ets_test_ordered_set, Role#role.role_name, #role.role_name) end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag index_read Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag5, TimeSet5, TimeMnesia5, TimeEtsOrder5]),
|
||||
%%
|
||||
%% %% delete
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeBag1, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb2:delete(ets_test_bag, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeSet1, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb3:delete(ets_test_set, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeMnesia1, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_mdb:delete(mnesia_test, Role#role.'_id') end, Roles]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {TimeEtsOrder1, _} = timer:tc(lists, foreach, [fun(Role) -> ms_cache_edb:delete(ets_test_ordered_set, Role#role.'_id') end, Roles]),
|
||||
%% ?INFO(?MODULE, "ets_test_bag delete Len = ~p, TimeBag = ~p, TimeSet = ~p, TimeMnesia = ~p, TimeEtsOrder = ~p",
|
||||
%% [erlang:length(Roles), TimeBag1, TimeSet1, TimeMnesia1, TimeEtsOrder1]),
|
||||
%%
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_role_batch() ->
|
||||
%% Roles = role_c:select_all(select_pool_name),
|
||||
%% test_role_batch(Roles, 1),
|
||||
%% test_role_batch(Roles, 1*1024),
|
||||
%% test_role_batch(Roles, 8*1024),
|
||||
%% test_role_batch(Roles, 16*1024),
|
||||
%% test_role_batch(Roles, 32*1024),
|
||||
%% test_role_batch(Roles, 64*1024),
|
||||
%% test_role_batch(Roles, 128*1024),
|
||||
%% test_role_batch(Roles, 256*1024),
|
||||
%% test_role_batch(Roles, 512*1024),
|
||||
%% test_role_batch(Roles, 1024*1024),
|
||||
%% test_role_batch(Roles, 2048*1024),
|
||||
%% test_role_batch(Roles, 4096*1024),
|
||||
%% ?OK.
|
||||
%%
|
||||
%%test_role_batch(Roles, BatchLen) ->
|
||||
%% RolesLen = erlang:length(Roles),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {Time1, _} = timer:tc(role_c, batch_insert, [update_pool_name, Roles, BatchLen]),
|
||||
%% ?INFO(?MODULE, "test_role_batch BatchLen = ~p, Time = ~p, RolesLen = ~p, QPS = ~p",
|
||||
%% [BatchLen div 1024, Time1, RolesLen, erlang:trunc(100000/(Time1/RolesLen))]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {Time2, _} = timer:tc(role_c, batch_insert, [update_pool_name, Roles, BatchLen]),
|
||||
%% ?INFO(?MODULE, "test_role_batch BatchLen = ~p, Time = ~p, RolesLen = ~p, QPS = ~p",
|
||||
%% [BatchLen div 1024, Time2, RolesLen, erlang:trunc(100000/(Time2/RolesLen))]),
|
||||
%% erlang:garbage_collect(erlang:self()),
|
||||
%% {Time3, _} = timer:tc(role_c, batch_insert, [update_pool_name, Roles, BatchLen]),
|
||||
%% ?INFO(?MODULE, "test_role_batch BatchLen = ~p, Time = ~p, RolesLen = ~p, QPS = ~p",
|
||||
%% [BatchLen div 1024, Time3, RolesLen, erlang:trunc(100000/(Time3/RolesLen))]),
|
||||
%% ?OK.
|
||||
Reference in New Issue
Block a user