You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
665 lines
17 KiB
665 lines
17 KiB
package dao |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"encoding/json" |
|
"fmt" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"go-common/app/job/main/search/conf" |
|
"go-common/app/job/main/search/model" |
|
"go-common/library/xstr" |
|
// "go-common/database/hbase" |
|
xsql "go-common/library/database/sql" |
|
"go-common/library/log" |
|
"go-common/library/log/infoc" |
|
"go-common/library/queue/databus" |
|
"go-common/library/stat/prom" |
|
|
|
"gopkg.in/olivere/elastic.v5" |
|
) |
|
|
|
var errorsCount = prom.BusinessErrCount |
|
|
|
const ( |
|
// business |
|
|
|
// search db name. for table attr,offset,manager. |
|
_searchDB = "search" |
|
) |
|
|
|
// App . |
|
type App interface { |
|
Business() string |
|
InitIndex(c context.Context) |
|
InitOffset(c context.Context) |
|
Offset(c context.Context) |
|
SetRecover(c context.Context, recoverID int64, recoverTime string, i int) |
|
IncrMessages(c context.Context) (length int, err error) |
|
AllMessages(c context.Context) (length int, err error) |
|
BulkIndex(c context.Context, start, end int, writeEntityIndex bool) (err error) |
|
Commit(c context.Context) (err error) |
|
Sleep(c context.Context) |
|
Size(c context.Context) (size int) |
|
} |
|
|
|
// Dao . |
|
type Dao struct { |
|
c *conf.Config |
|
// smsClient |
|
sms *sms |
|
// search db |
|
SearchDB *xsql.DB |
|
// hbase *hbase.Client |
|
BusinessPool map[string]model.BsnAppInfo |
|
AttrPool map[string]*model.Attrs |
|
AppPool map[string]App |
|
DBPool map[string]*xsql.DB |
|
ESPool map[string]*elastic.Client |
|
DatabusPool map[string]*databus.Databus |
|
InfoCPool map[string]*infoc.Infoc |
|
} |
|
|
|
// New . |
|
func New(c *conf.Config) (d *Dao) { |
|
d = &Dao{ |
|
c: c, |
|
DBPool: newDbPool(c), |
|
} |
|
// check search db |
|
if d.SearchDB = d.DBPool[_searchDB]; d.SearchDB == nil { |
|
panic("SearchDB must config") |
|
} |
|
d.sms = newSMS(d) |
|
d.BusinessPool = newBusinessPool(d) |
|
d.AttrPool = newAttrPool(d) |
|
d.ESPool = newEsPool(c, d) |
|
// consumer |
|
d.DatabusPool = newDatabusPool(c, d) |
|
d.InfoCPool = newInfoCPool(c, d) |
|
return |
|
} |
|
|
|
// newDatabusPool . |
|
func newDatabusPool(c *conf.Config, d *Dao) (pool map[string]*databus.Databus) { |
|
pool = make(map[string]*databus.Databus) |
|
if c.Business.Index { |
|
return |
|
} |
|
for name := range d.BusinessPool { |
|
if config, ok := c.Databus[name]; ok { |
|
pool[name] = databus.New(config) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// newInfoCPool . |
|
func newInfoCPool(c *conf.Config, d *Dao) (pool map[string]*infoc.Infoc) { |
|
pool = map[string]*infoc.Infoc{} |
|
if c.Business.Index { |
|
return |
|
} |
|
for k := range d.BusinessPool { |
|
if n, ok := c.InfoC[k]; ok { |
|
pool[k] = infoc.New(n) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// newBusinessPool all appid info from one business |
|
func newBusinessPool(d *Dao) (pool map[string]model.BsnAppInfo) { |
|
pool = map[string]model.BsnAppInfo{} |
|
if bns, err := newBusiness(d, d.c.Business.Env); err == nil { |
|
for _, v := range bns.bInfo.AppInfo { |
|
if v.AppID != "" { |
|
pool[v.AppID] = v |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// newAttrPool . |
|
func newAttrPool(d *Dao) (pool map[string]*model.Attrs) { |
|
pool = make(map[string]*model.Attrs) |
|
for k := range d.BusinessPool { |
|
ar := newAttr(d, k) |
|
pool[k] = ar.attrs |
|
} |
|
//fmt.Println("strace:attr-pool>", pool) |
|
return |
|
} |
|
|
|
// SetRecover set recover. |
|
func (d *Dao) SetRecover(c context.Context, appid string, recoverID int64, recoverTime string, i int) { |
|
d.AppPool[appid].SetRecover(c, recoverID, recoverTime, i) |
|
} |
|
|
|
// newDbPool db combo |
|
func newDbPool(c *conf.Config) (pool map[string]*xsql.DB) { |
|
pool = make(map[string]*xsql.DB) |
|
for dbName, config := range c.DB { |
|
pool[dbName] = xsql.NewMySQL(config) |
|
} |
|
return |
|
} |
|
|
|
// newEsCluster cluster action |
|
func newEsPool(c *conf.Config, d *Dao) (esCluster map[string]*elastic.Client) { |
|
esCluster = make(map[string]*elastic.Client) |
|
for esName, e := range c.Es { |
|
if client, err := elastic.NewClient(elastic.SetURL(e.Addr...)); err == nil { |
|
esCluster[esName] = client |
|
} else { |
|
d.PromError("es:集群连接失败", "cluster: %s, %v", esName, err) |
|
if err := d.SendSMS(fmt.Sprintf("[search-job]%s集群连接失败", esName)); err != nil { |
|
d.PromError("es:集群连接短信失败", "cluster: %s, %v", esName, err) |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// PromError . |
|
func (d *Dao) PromError(name string, format string, args ...interface{}) { |
|
errorsCount.Incr(name) |
|
log.Error(format, args) |
|
} |
|
|
|
// Close close dao |
|
func (d *Dao) Close() { |
|
for _, db := range d.DBPool { |
|
db.Close() |
|
} |
|
} |
|
|
|
// Ping health of db. |
|
func (d *Dao) Ping(c context.Context) (err error) { |
|
// TODO 循环ping |
|
if err = d.SearchDB.Ping(c); err != nil { |
|
d.PromError("db:ping", "") |
|
return |
|
} |
|
if err = d.pingESCluster(c); err != nil { |
|
d.PromError("es:ping", "d.pingESCluster error(%v)", err) |
|
return |
|
} |
|
return |
|
} |
|
|
|
// GetAliases get all aliases by indexAliasPrefix |
|
func (d *Dao) GetAliases(esName, indexAliasPrefix string) (aliases map[string]bool, err error) { |
|
aliases = map[string]bool{} |
|
if _, ok := d.ESPool[esName]; !ok { |
|
log.Error("GetAliases 集群不存在 (%s)", esName) |
|
return |
|
} |
|
if aliasesRes, err := d.ESPool[esName].Aliases().Index(indexAliasPrefix + "*").Do(context.TODO()); err != nil { |
|
log.Error("GetAliases(%s*) failed", indexAliasPrefix) |
|
} else { |
|
for _, indexDetails := range aliasesRes.Indices { |
|
for _, v := range indexDetails.Aliases { |
|
if v.AliasName != "" { |
|
aliases[v.AliasName] = true |
|
} |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// InitIndex create entity indecies & aliases if necessary |
|
func (d *Dao) InitIndex(c context.Context, aliases map[string]bool, esName, indexAliasName, indexEntityName, indexMapping string) { |
|
if indexMapping == "" { |
|
log.Error("indexEntityName(%s) mapping is epmty", indexEntityName) |
|
return |
|
} |
|
for { |
|
exists, err := d.ESPool[esName].IndexExists(indexEntityName).Do(c) |
|
if err != nil { |
|
time.Sleep(time.Second * 3) |
|
continue |
|
} |
|
if !exists { |
|
if _, err := d.ESPool[esName].CreateIndex(indexEntityName).Body(indexMapping).Do(c); err != nil { |
|
log.Error("indexEntityName(%s) create err(%v)", indexEntityName, err) |
|
time.Sleep(time.Second * 3) |
|
continue |
|
} |
|
} |
|
break |
|
} |
|
// add aliases if necessary |
|
if aliases != nil && indexAliasName != indexEntityName { |
|
if _, ok := aliases[indexAliasName]; !ok { |
|
if _, err := d.ESPool[esName].Alias().Add(indexEntityName, indexAliasName).Do(context.TODO()); err != nil { |
|
log.Error("indexEntityName(%s) failed to add alias indexAliasName(%s) err(%v)", indexEntityName, indexAliasName, err) |
|
} |
|
} |
|
} |
|
} |
|
|
|
// InitOffset init offset to offset table . |
|
func (d *Dao) InitOffset(c context.Context, offset *model.LoopOffset, attrs *model.Attrs, arr []string) { |
|
for { |
|
if err := d.bulkInitOffset(c, offset, attrs, arr); err != nil { |
|
log.Error("project(%s) initOffset(%v)", attrs.AppID, err) |
|
time.Sleep(time.Second * 3) |
|
continue |
|
} |
|
break |
|
} |
|
} |
|
|
|
// InitMapData init each field struct |
|
func InitMapData(fields []string) (item model.MapData, row []interface{}) { |
|
item = make(map[string]interface{}) |
|
for _, v := range fields { |
|
item[v] = new(interface{}) |
|
} |
|
for _, v := range fields { |
|
row = append(row, item[v]) |
|
} |
|
return |
|
} |
|
|
|
// UpdateOffsetByMap . |
|
func UpdateOffsetByMap(offsets *model.LoopOffset, mapData ...model.MapData) { |
|
var ( |
|
id int64 |
|
mtime string |
|
) |
|
length := len(mapData) |
|
if length == 0 { |
|
return |
|
} |
|
offsetTime := offsets.OffsetTime |
|
lastRes := mapData[length-1] |
|
id = lastRes.PrimaryID() |
|
lastMtime := lastRes.StrMTime() |
|
//fmt.Println("real", lastMtime, id, offsets.OffsetID) |
|
if (id != offsets.OffsetID) && (offsetTime == lastMtime) { |
|
offsets.IsLoop = true |
|
} else { |
|
if offsets.IsLoop { |
|
for _, p := range mapData { |
|
tempMtime := p.StrMTime() |
|
if tempMtime == offsetTime { |
|
continue |
|
} |
|
id = p.PrimaryID() |
|
mtime = tempMtime |
|
break |
|
} |
|
} else { |
|
mtime = lastMtime |
|
} |
|
offsets.IsLoop = false |
|
} |
|
offsets.SetTempOffset(id, mtime) |
|
} |
|
|
|
// CommitOffset . |
|
func (d *Dao) CommitOffset(c context.Context, offset *model.LoopOffset, appid, tableName string) (err error) { |
|
if offset.TempOffsetID != 0 { |
|
offset.SetOffset(offset.TempOffsetID, "") |
|
} |
|
if offset.TempOffsetTime != "" { |
|
offset.SetOffset(0, offset.TempOffsetTime) |
|
} |
|
if offset.TempRecoverID >= 0 { |
|
offset.SetRecoverOffset(offset.TempRecoverID, "") |
|
} |
|
if offset.TempRecoverTime != "" { |
|
offset.SetRecoverOffset(-1, offset.TempRecoverTime) |
|
} |
|
err = d.updateOffset(c, offset, appid, tableName) |
|
return |
|
} |
|
|
|
// JSON2map json to map. |
|
func (d *Dao) JSON2map(rowJSON json.RawMessage) (result map[string]interface{}, err error) { |
|
decoder := json.NewDecoder(bytes.NewReader(rowJSON)) |
|
decoder.UseNumber() |
|
if err = decoder.Decode(&result); err != nil { |
|
log.Error("JSON2map.Unmarshal(%s) error(%v)", rowJSON, err) |
|
return nil, err |
|
} |
|
// json.Number转int64 |
|
for k, v := range result { |
|
switch t := v.(type) { |
|
case json.Number: |
|
if result[k], err = t.Int64(); err != nil { |
|
log.Error("JSON2map.json.Number(%v)(%v)", t, err) |
|
return nil, err |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// ExtraData . |
|
func (d *Dao) ExtraData(c context.Context, mapData []model.MapData, attrs *model.Attrs, way string, tags []string) (md []model.MapData, err error) { |
|
md = mapData |
|
switch way { |
|
case "db": |
|
for i, item := range mapData { |
|
item.TransData(attrs) |
|
for k, v := range item { |
|
md[i][k] = v |
|
} |
|
} |
|
case "dtb": |
|
for i, item := range mapData { |
|
item.TransDtb(attrs) |
|
for k, v := range item { |
|
md[i][k] = v |
|
} |
|
} |
|
} |
|
for _, ex := range attrs.DataExtras { |
|
// db exists or not |
|
if _, ok := d.DBPool[ex.DBName]; !ok { |
|
log.Error("ExtraData d.DBPool excludes:%s", ex.DBName) |
|
continue |
|
} |
|
if len(tags) != 0 { |
|
for _, v := range tags { |
|
if v != ex.Tag { |
|
continue |
|
} |
|
switch ex.Type { |
|
case "slice": |
|
md, err = d.extraDataSlice(c, md, attrs, ex) |
|
default: |
|
md, err = d.extraDataDefault(c, md, attrs, ex) |
|
} |
|
} |
|
} else { |
|
switch ex.Type { |
|
case "slice": |
|
md, err = d.extraDataSlice(c, md, attrs, ex) |
|
default: |
|
md, err = d.extraDataDefault(c, md, attrs, ex) |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// extraData-default |
|
func (d *Dao) extraDataDefault(c context.Context, mapData []model.MapData, attrs *model.Attrs, ex model.AttrDataExtra) (md []model.MapData, err error) { |
|
md = mapData |
|
// filter ids from in_fields |
|
var ( |
|
ids []int64 |
|
items map[int64]model.MapData |
|
include []string |
|
) |
|
cdtInField := ex.Condition["in_field"] |
|
items = make(map[int64]model.MapData) |
|
if cld, ok := ex.Condition["include"]; ok { |
|
include = strings.Split(cld, "=") |
|
} |
|
var rows *xsql.Rows |
|
if cdtInFields := strings.Split(cdtInField, ","); len(cdtInFields) == 1 { //FIXME 支持主键多个条件定位一条数据 |
|
for _, m := range mapData { |
|
if v, ok := m[cdtInField]; ok { |
|
if len(include) >= 2 { //TODO 支持多种 |
|
if cldVal, ok := m[include[0]]; ok && strconv.FormatInt(cldVal.(int64), 10) == include[1] { |
|
ids = append(ids, v.(int64)) |
|
} |
|
} else { |
|
ids = append(ids, v.(int64)) //TODO 加去重 |
|
} |
|
} |
|
} |
|
// query extra data |
|
//TODO 如果分表太多的业务,单次循环size设置过大一下子来50万的数据,where in一个表会拒绝请求或超时 |
|
if len(ids) > 0 { |
|
if tableFormat := strings.Split(ex.TableFormat, ","); ex.TableFormat == "" || tableFormat[0] == "single" { |
|
i := 0 |
|
flag := false |
|
//TODO 缺点:耗内存 |
|
for { |
|
var id []int64 |
|
if (i+1)*200 < len(ids) { |
|
id = ids[i*200 : (i+1)*200] |
|
} else { |
|
id = ids[i*200:] |
|
flag = true |
|
} |
|
rows, err = d.DBPool[ex.DBName].Query(c, fmt.Sprintf(ex.SQL, xstr.JoinInts(id))+" and 1 = ? ", 1) |
|
if err != nil { |
|
log.Error("extraDataDefault db.Query error(%v)", err) |
|
return |
|
} |
|
for rows.Next() { |
|
item, row := InitMapData(ex.Fields) |
|
if err = rows.Scan(row...); err != nil { |
|
log.Error("extraDataDefault rows.Scan() error(%v)", err) |
|
continue |
|
} |
|
if v, ok := item[ex.InField]; ok { |
|
if v2, ok := v.(*interface{}); ok { |
|
item.TransData(attrs) |
|
items[(*v2).(int64)] = item |
|
} |
|
} |
|
// fmt.Println(item) |
|
} |
|
rows.Close() |
|
i++ |
|
if flag { |
|
break |
|
} |
|
} |
|
} else if tableFormat[0] == "int" { |
|
formatData := make(map[int64][]int64) |
|
var dbid = []int64{} |
|
if len(tableFormat) >= 6 { // 弹幕举报根据文章id来分表 dmid进行匹配 |
|
for _, m := range mapData { |
|
if v, ok := m[tableFormat[5]]; ok { |
|
dbid = append(dbid, v.(int64)) // 加去重 |
|
} |
|
} |
|
} else { |
|
dbid = ids |
|
} |
|
if len(dbid) != len(ids) { |
|
log.Error("tableFormat[5] len error(%v)(%v)", len(dbid), len(ids)) |
|
return |
|
} |
|
for i := 0; i < len(ids); i++ { |
|
d, e := strconv.ParseInt(tableFormat[2], 10, 64) |
|
if e != nil { |
|
log.Error("extraDataDefault strconv.Atoi() error(%v)", e) |
|
continue |
|
} |
|
d = dbid[i] % (d + 1) |
|
if d < 0 { //可能有脏数据 |
|
continue |
|
} |
|
formatData[d] = append(formatData[d], ids[i]) |
|
} |
|
for v, k := range formatData { |
|
rows, err = d.DBPool[ex.DBName].Query(c, fmt.Sprintf(ex.SQL, v, xstr.JoinInts(k))+" and 1 = ? ", 1) |
|
if err != nil { |
|
log.Error("extraDataDefaultTableFormat db.Query error(%v)", err) |
|
return |
|
} |
|
for rows.Next() { |
|
item, row := InitMapData(ex.Fields) |
|
if err = rows.Scan(row...); err != nil { |
|
log.Error("extraDataDefaultTableFormat rows.Scan() error(%v)", err) |
|
continue |
|
} |
|
if v, ok := item[ex.InField]; ok { |
|
if v2, ok := v.(*interface{}); ok { |
|
item.TransData(attrs) |
|
items[(*v2).(int64)] = item |
|
} |
|
} |
|
} |
|
rows.Close() |
|
} |
|
} |
|
} |
|
// fmt.Println("ids:", ids, "items:", items) |
|
// merge data |
|
for i, m := range mapData { |
|
if len(include) >= 2 { //TODO 支持多种 |
|
if cldVal, ok := m[include[0]]; !ok || strconv.FormatInt(cldVal.(int64), 10) != include[1] { |
|
continue |
|
} |
|
} |
|
if k, ok := m[cdtInField]; ok { |
|
if item, ok := items[k.(int64)]; ok { |
|
for _, v := range ex.RemoveFields { |
|
delete(item, v) |
|
} |
|
item.TransData(attrs) |
|
for k, v := range item { |
|
md[i][k] = v |
|
} |
|
} |
|
} |
|
} |
|
//fmt.Println(md) |
|
} else { |
|
for i, m := range mapData { |
|
var value []interface{} |
|
for _, v := range cdtInFields { |
|
value = append(value, m[v]) |
|
} |
|
rows, err = d.DBPool[ex.DBName].Query(c, ex.SQL, value...) |
|
if err != nil { |
|
log.Error("extraDataDefault db.Query error(%v)", err) |
|
return |
|
} |
|
for rows.Next() { |
|
item, row := InitMapData(ex.Fields) |
|
if err = rows.Scan(row...); err != nil { |
|
log.Error("extraDataDefault rows.Scan() error(%v)", err) |
|
continue |
|
} |
|
item.TransData(attrs) |
|
for _, v := range ex.RemoveFields { |
|
delete(item, v) |
|
} |
|
for k, v := range item { |
|
md[i][k] = v |
|
} |
|
} |
|
rows.Close() |
|
} |
|
} |
|
return |
|
} |
|
|
|
// extraData-slice |
|
func (d *Dao) extraDataSlice(c context.Context, mapData []model.MapData, attrs *model.Attrs, ex model.AttrDataExtra) (md []model.MapData, err error) { |
|
md = mapData |
|
// filter ids from in_fields |
|
var ( |
|
ids []int64 |
|
items map[string]map[string][]interface{} |
|
include []string |
|
) |
|
cdtInField := ex.Condition["in_field"] |
|
items = make(map[string]map[string][]interface{}) |
|
sliceFields := strings.Split(ex.SliceField, ",") |
|
if cld, ok := ex.Condition["include"]; ok { |
|
include = strings.Split(cld, "=") |
|
} |
|
for _, m := range mapData { |
|
if v, ok := m[cdtInField]; ok { |
|
if len(include) >= 2 { //TODO 支持多种 |
|
if cldVal, ok := m[include[0]]; ok && strconv.FormatInt(cldVal.(int64), 10) == include[1] { |
|
ids = append(ids, v.(int64)) |
|
} |
|
} else { |
|
ids = append(ids, v.(int64)) //TODO 加去重 |
|
} |
|
} |
|
} |
|
// query extra data |
|
if len(ids) > 0 { |
|
var rows *xsql.Rows |
|
rows, err = d.DBPool[ex.DBName].Query(c, fmt.Sprintf(ex.SQL, xstr.JoinInts(ids))+" and 1 = ? ", 1) |
|
if err != nil { |
|
log.Error("extraDataSlice db.Query error(%v)", err) |
|
return |
|
} |
|
for rows.Next() { |
|
item, row := InitMapData(ex.Fields) |
|
if err = rows.Scan(row...); err != nil { |
|
log.Error("extraDataSlice rows.Scan() error(%v)", err) |
|
continue |
|
} |
|
if v, ok := item[ex.InField]; ok { |
|
if v2, ok := v.(*interface{}); ok { |
|
var key string |
|
switch (*v2).(type) { |
|
case int, int8, int16, int32, int64: |
|
key = strconv.FormatInt((*v2).(int64), 10) |
|
case []uint, []uint8, []uint16, []uint32, []uint64: |
|
key = string((*v2).([]byte)) |
|
} |
|
for _, sf := range sliceFields { |
|
if _, ok := items[key]; !ok { |
|
items[key] = make(map[string][]interface{}) |
|
} |
|
var res interface{} |
|
if v3, ok := item[sf].(*interface{}); ok { |
|
switch (*v3).(type) { |
|
case []uint, []uint8, []uint16, []uint32, []uint64: |
|
res = string((*v3).([]byte)) |
|
default: |
|
res = v3 |
|
} |
|
} |
|
items[key][sf] = append(items[key][sf], res) |
|
} |
|
} |
|
} |
|
} |
|
rows.Close() |
|
} |
|
//log.Info("items:%v", items) |
|
// merge data |
|
for i, m := range mapData { |
|
if len(include) >= 2 { //TODO 支持多种 |
|
if cldVal, ok := m[include[0]]; !ok || strconv.FormatInt(cldVal.(int64), 10) != include[1] { |
|
continue |
|
} |
|
} |
|
if v, ok := m[cdtInField]; ok { |
|
if item, ok := items[strconv.FormatInt(v.(int64), 10)]; ok { |
|
for _, sf := range sliceFields { |
|
if list, ok := item[sf]; ok { |
|
md[i][sf] = list |
|
} |
|
} |
|
} else { |
|
for _, sf := range sliceFields { |
|
md[i][sf] = []int64{} |
|
} |
|
} |
|
} |
|
} |
|
// for _, v := range md { |
|
// log.Info("md:%v", v) |
|
// } |
|
return |
|
} |
|
|
|
// GetConfig . |
|
func (d *Dao) GetConfig(c context.Context) *conf.Config { |
|
return d.c |
|
}
|
|
|