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.

292 lines
8.9 KiB

5 years ago
6 years ago
5 years ago
6 years ago
6 years ago
5 years ago
  1. package shell
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "sync"
  8. "time"
  9. "google.golang.org/grpc"
  10. "github.com/chrislusf/seaweedfs/weed/operation"
  11. "github.com/chrislusf/seaweedfs/weed/pb/master_pb"
  12. "github.com/chrislusf/seaweedfs/weed/pb/volume_server_pb"
  13. "github.com/chrislusf/seaweedfs/weed/storage/erasure_coding"
  14. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  15. "github.com/chrislusf/seaweedfs/weed/wdclient"
  16. )
  17. func init() {
  18. Commands = append(Commands, &commandEcEncode{})
  19. }
  20. type commandEcEncode struct {
  21. }
  22. func (c *commandEcEncode) Name() string {
  23. return "ec.encode"
  24. }
  25. func (c *commandEcEncode) Help() string {
  26. return `apply erasure coding to a volume
  27. ec.encode [-collection=""] [-fullPercent=95] [-quietFor=1h]
  28. ec.encode [-collection=""] [-volumeId=<volume_id>]
  29. This command will:
  30. 1. freeze one volume
  31. 2. apply erasure coding to the volume
  32. 3. move the encoded shards to multiple volume servers
  33. The erasure coding is 10.4. So ideally you have more than 14 volume servers, and you can afford
  34. to lose 4 volume servers.
  35. If the number of volumes are not high, the worst case is that you only have 4 volume servers,
  36. and the shards are spread as 4,4,3,3, respectively. You can afford to lose one volume server.
  37. If you only have less than 4 volume servers, with erasure coding, at least you can afford to
  38. have 4 corrupted shard files.
  39. `
  40. }
  41. func (c *commandEcEncode) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  42. if err = commandEnv.confirmIsLocked(); err != nil {
  43. return
  44. }
  45. encodeCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  46. volumeId := encodeCommand.Int("volumeId", 0, "the volume id")
  47. collection := encodeCommand.String("collection", "", "the collection name")
  48. fullPercentage := encodeCommand.Float64("fullPercent", 95, "the volume reaches the percentage of max volume size")
  49. quietPeriod := encodeCommand.Duration("quietFor", time.Hour, "select volumes without no writes for this period")
  50. if err = encodeCommand.Parse(args); err != nil {
  51. return nil
  52. }
  53. vid := needle.VolumeId(*volumeId)
  54. // volumeId is provided
  55. if vid != 0 {
  56. return doEcEncode(commandEnv, *collection, vid)
  57. }
  58. // apply to all volumes in the collection
  59. volumeIds, err := collectVolumeIdsForEcEncode(commandEnv, *collection, *fullPercentage, *quietPeriod)
  60. if err != nil {
  61. return err
  62. }
  63. fmt.Printf("ec encode volumes: %v\n", volumeIds)
  64. for _, vid := range volumeIds {
  65. if err = doEcEncode(commandEnv, *collection, vid); err != nil {
  66. return err
  67. }
  68. }
  69. return nil
  70. }
  71. func doEcEncode(commandEnv *CommandEnv, collection string, vid needle.VolumeId) (err error) {
  72. // find volume location
  73. locations, found := commandEnv.MasterClient.GetLocations(uint32(vid))
  74. if !found {
  75. return fmt.Errorf("volume %d not found", vid)
  76. }
  77. // fmt.Printf("found ec %d shards on %v\n", vid, locations)
  78. // mark the volume as readonly
  79. err = markVolumeReadonly(commandEnv.option.GrpcDialOption, needle.VolumeId(vid), locations)
  80. if err != nil {
  81. return fmt.Errorf("mark volume %d as readonly on %s: %v", vid, locations[0].Url, err)
  82. }
  83. // generate ec shards
  84. err = generateEcShards(commandEnv.option.GrpcDialOption, needle.VolumeId(vid), collection, locations[0].Url)
  85. if err != nil {
  86. return fmt.Errorf("generate ec shards for volume %d on %s: %v", vid, locations[0].Url, err)
  87. }
  88. // balance the ec shards to current cluster
  89. err = spreadEcShards(commandEnv, vid, collection, locations)
  90. if err != nil {
  91. return fmt.Errorf("spread ec shards for volume %d from %s: %v", vid, locations[0].Url, err)
  92. }
  93. return nil
  94. }
  95. func markVolumeReadonly(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, locations []wdclient.Location) error {
  96. for _, location := range locations {
  97. err := operation.WithVolumeServerClient(location.Url, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  98. _, markErr := volumeServerClient.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{
  99. VolumeId: uint32(volumeId),
  100. })
  101. return markErr
  102. })
  103. if err != nil {
  104. return err
  105. }
  106. }
  107. return nil
  108. }
  109. func generateEcShards(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, collection string, sourceVolumeServer string) error {
  110. err := operation.WithVolumeServerClient(sourceVolumeServer, grpcDialOption, func(volumeServerClient volume_server_pb.VolumeServerClient) error {
  111. _, genErr := volumeServerClient.VolumeEcShardsGenerate(context.Background(), &volume_server_pb.VolumeEcShardsGenerateRequest{
  112. VolumeId: uint32(volumeId),
  113. Collection: collection,
  114. })
  115. return genErr
  116. })
  117. return err
  118. }
  119. func spreadEcShards(commandEnv *CommandEnv, volumeId needle.VolumeId, collection string, existingLocations []wdclient.Location) (err error) {
  120. allEcNodes, totalFreeEcSlots, err := collectEcNodes(commandEnv, "")
  121. if err != nil {
  122. return err
  123. }
  124. if totalFreeEcSlots < erasure_coding.TotalShardsCount {
  125. return fmt.Errorf("not enough free ec shard slots. only %d left", totalFreeEcSlots)
  126. }
  127. allocatedDataNodes := allEcNodes
  128. if len(allocatedDataNodes) > erasure_coding.TotalShardsCount {
  129. allocatedDataNodes = allocatedDataNodes[:erasure_coding.TotalShardsCount]
  130. }
  131. // calculate how many shards to allocate for these servers
  132. allocatedEcIds := balancedEcDistribution(allocatedDataNodes)
  133. // ask the data nodes to copy from the source volume server
  134. copiedShardIds, err := parallelCopyEcShardsFromSource(commandEnv.option.GrpcDialOption, allocatedDataNodes, allocatedEcIds, volumeId, collection, existingLocations[0])
  135. if err != nil {
  136. return err
  137. }
  138. // unmount the to be deleted shards
  139. err = unmountEcShards(commandEnv.option.GrpcDialOption, volumeId, existingLocations[0].Url, copiedShardIds)
  140. if err != nil {
  141. return err
  142. }
  143. // ask the source volume server to clean up copied ec shards
  144. err = sourceServerDeleteEcShards(commandEnv.option.GrpcDialOption, collection, volumeId, existingLocations[0].Url, copiedShardIds)
  145. if err != nil {
  146. return fmt.Errorf("source delete copied ecShards %s %d.%v: %v", existingLocations[0].Url, volumeId, copiedShardIds, err)
  147. }
  148. // ask the source volume server to delete the original volume
  149. for _, location := range existingLocations {
  150. fmt.Printf("delete volume %d from %s\n", volumeId, location.Url)
  151. err = deleteVolume(commandEnv.option.GrpcDialOption, volumeId, location.Url)
  152. if err != nil {
  153. return fmt.Errorf("deleteVolume %s volume %d: %v", location.Url, volumeId, err)
  154. }
  155. }
  156. return err
  157. }
  158. func parallelCopyEcShardsFromSource(grpcDialOption grpc.DialOption, targetServers []*EcNode, allocatedEcIds [][]uint32, volumeId needle.VolumeId, collection string, existingLocation wdclient.Location) (actuallyCopied []uint32, err error) {
  159. // parallelize
  160. shardIdChan := make(chan []uint32, len(targetServers))
  161. var wg sync.WaitGroup
  162. for i, server := range targetServers {
  163. if len(allocatedEcIds[i]) <= 0 {
  164. continue
  165. }
  166. wg.Add(1)
  167. go func(server *EcNode, allocatedEcShardIds []uint32) {
  168. defer wg.Done()
  169. copiedShardIds, copyErr := oneServerCopyAndMountEcShardsFromSource(grpcDialOption, server,
  170. allocatedEcShardIds, volumeId, collection, existingLocation.Url)
  171. if copyErr != nil {
  172. err = copyErr
  173. } else {
  174. shardIdChan <- copiedShardIds
  175. server.addEcVolumeShards(volumeId, collection, copiedShardIds)
  176. }
  177. }(server, allocatedEcIds[i])
  178. }
  179. wg.Wait()
  180. close(shardIdChan)
  181. if err != nil {
  182. return nil, err
  183. }
  184. for shardIds := range shardIdChan {
  185. actuallyCopied = append(actuallyCopied, shardIds...)
  186. }
  187. return
  188. }
  189. func balancedEcDistribution(servers []*EcNode) (allocated [][]uint32) {
  190. allocated = make([][]uint32, len(servers))
  191. allocatedShardIdIndex := uint32(0)
  192. serverIndex := 0
  193. for allocatedShardIdIndex < erasure_coding.TotalShardsCount {
  194. if servers[serverIndex].freeEcSlot > 0 {
  195. allocated[serverIndex] = append(allocated[serverIndex], allocatedShardIdIndex)
  196. allocatedShardIdIndex++
  197. }
  198. serverIndex++
  199. if serverIndex >= len(servers) {
  200. serverIndex = 0
  201. }
  202. }
  203. return allocated
  204. }
  205. func collectVolumeIdsForEcEncode(commandEnv *CommandEnv, selectedCollection string, fullPercentage float64, quietPeriod time.Duration) (vids []needle.VolumeId, err error) {
  206. var resp *master_pb.VolumeListResponse
  207. err = commandEnv.MasterClient.WithClient(func(client master_pb.SeaweedClient) error {
  208. resp, err = client.VolumeList(context.Background(), &master_pb.VolumeListRequest{})
  209. return err
  210. })
  211. if err != nil {
  212. return
  213. }
  214. quietSeconds := int64(quietPeriod / time.Second)
  215. nowUnixSeconds := time.Now().Unix()
  216. fmt.Printf("ec encode volumes quiet for: %d seconds\n", quietSeconds)
  217. vidMap := make(map[uint32]bool)
  218. eachDataNode(resp.TopologyInfo, func(dc string, rack RackId, dn *master_pb.DataNodeInfo) {
  219. for _, v := range dn.VolumeInfos {
  220. if v.Collection == selectedCollection && v.ModifiedAtSecond+quietSeconds < nowUnixSeconds {
  221. if float64(v.Size) > fullPercentage/100*float64(resp.VolumeSizeLimitMb)*1024*1024 {
  222. vidMap[v.Id] = true
  223. }
  224. }
  225. }
  226. })
  227. for vid := range vidMap {
  228. vids = append(vids, needle.VolumeId(vid))
  229. }
  230. return
  231. }