下面这篇文章就是对它们的介绍。
注意:本功能需要Wowza Streaming Engine™ 4.6.0及以上版本的支持。 Wowza Transcoder在运行时会调用下面的Interface ITranscoderVideoLoadBalancer:
public interface ITranscoderVideoLoadBalancer { public abstract void init(IServer server, TranscoderContextServer transcoderContextServer); public abstract void onHardwareInspection(TranscoderContextServer transcoderContextServer); public abstract void onTranscoderSessionCreate(LiveStreamTranscoder liveStreamTranscoder); public abstract void onTranscoderSessionInit(LiveStreamTranscoder liveStreamTranscoder); public abstract void onTranscoderSessionDestroy(LiveStreamTranscoder liveStreamTranscoder); public abstract void onTranscoderSessionLoadBalance(LiveStreamTranscoder liveStreamTranscoder); }其中:
- init 是当服务器启动时被调用
- onHardwareInspection是当Transcoder刚启动正在检测显卡等包含GPU的硬件加速设备资源时被调用
- onTranscoderSessionCreate当一个转码任务(session)创建时被调用
- onTranscoderSessionInit当一个转码任务(session)完成初始化且转码模板被读取完毕时被调用
- onTranscoderSessionDestroy当一个转码任务(session)销毁时被调用
- onTranscoderSessionLoadBalance当一个转码任务正处在decoder、scaler以及encoder的动作被初始化时被调用
配置 ITranscodeVideoLoadBalancer 接口的实现类
要使用ITranscoderVideoLoadBalancer接口,你需要按以下操作:
-
创建一个class,继承TranscoderVideoLoadBalancerBase (它实现了上面介绍的ITranscoderVideoLoadBalancer接口),然后重载接口中的方法。
要了解更多,请阅读例子 class - TranscoderVideoLoadBalancerCUDASimple.
-
然后在[install-dir]/conf/Server.xml文件中的添加一个server级别的参数,指明这个实现类的完整类包名:
<Property> <Name>transcoderVideoLoadBalancerClass</Name> <Value>[custom-class-path]</Value> </Property>
其中[custom-class-path]就是你的实现类的完整类包名。 例如,如果你使用的就是Wowza内部自带的这个TranscoderVideoLoadBalancerCUDASimple实现类,你就该按下面配置:
<Property> <Name>transcoderVideoLoadBalancerClass</Name> <Value>com.wowza.wms.transcoder.model.TranscoderVideoLoadBalancerCUDASimple</Value> </Property>
- (可选)如果你的多个GPU性能各不一样,你还可以用transcoderVideoLoadBalancerCUDASimpleGPUWeights 参数为每一个GPU设置不同的权重。具体请阅读不同性能的多个GPU之间的负载均衡。
例子 class - TranscoderVideoLoadBalancerCUDASimple
从Wowza Streaming Engine (4.5.0.01)开始,TranscoderVideoLoadBalancerCUDASimple) 就已经内置在Wowza中了。你不用做任何开发工作就可以直接使用。 它的负载均衡机制是将每一个独立的转码任务(session)的所有工作都分配在一个GPU上,也就说一个转码任务内部的工作不会在多个GPU之间来回切换。
import java.util.*; import com.wowza.util.*; import com.wowza.wms.application.*; import com.wowza.wms.logging.*; import com.wowza.wms.media.model.*; import com.wowza.wms.server.*; public class TranscoderVideoLoadBalancerCUDASimple extends TranscoderVideoLoadBalancerBase { private static final Class<TranscoderVideoLoadBalancerCUDASimple> CLASS = TranscoderVideoLoadBalancerCUDASimple.class; private static final String CLASSNAME = "TranscoderVideoLoadBalancerCUDASimple"; public static final int DEFAULT_GPU_WEIGHT_SCALE = 1; public static final int DEFAULT_WEIGHT_FACTOR_ENCODE = 5; public static final int DEFAULT_WEIGHT_FACTOR_DECODE = 1; public static final int DEFAULT_WEIGHT_FACTOR_SCALE = 1; public static final int LOAD_MAG = 1000; public static final String PROPNAME_TRANSCODER_SESSION = "TranscoderVideoLoadBalancerCUDASimpleSessionInfo"; class SessionInfo { private int gpuid = 0; private long load = 0; public SessionInfo(int gpuid, long load) { this.gpuid = gpuid; this.load = load; } } class GPUInfo { private int gpuid = 0; private long currentLoad = 0; private int weight = 0; private int getWeight() { return this.weight; } private long getUnWeightedLoad() { return currentLoad; } private long getWeightedLoad() { long load = 0; if (weight > 0) load = (currentLoad*gpuWeightScale)/weight; return load; } } private Object lock = new Object(); private TranscoderContextServer transcoderContextServer = null; private boolean available = false; private int countGPU = 0; private int gpuWeightScale = DEFAULT_GPU_WEIGHT_SCALE; private int[] gpuWeights = null; private int weightFactorEncode = DEFAULT_WEIGHT_FACTOR_ENCODE; private int weightFactorDecode = DEFAULT_WEIGHT_FACTOR_DECODE; private int weightFactorScale = DEFAULT_WEIGHT_FACTOR_SCALE; private GPUInfo[] gpuInfos = null; @Override public void init(IServer server, TranscoderContextServer transcoderContextServer) { this.transcoderContextServer = transcoderContextServer; WMSProperties props = server.getProperties(); this.weightFactorEncode = props.getPropertyInt("transcoderVideoLoadBalancerCUDASimpleWeightFactorEncode", this.weightFactorEncode); this.weightFactorDecode = props.getPropertyInt("transcoderVideoLoadBalancerCUDASimpleWeightFactorDecode", this.weightFactorDecode); this.weightFactorScale = props.getPropertyInt("transcoderVideoLoadBalancerCUDASimpleWeightFactorScale", this.weightFactorScale); String weightsStr = props.getPropertyStr("transcoderVideoLoadBalancerCUDASimpleGPUWeights", null); if (weightsStr != null) { String[] values = weightsStr.split(","); int maxWeight = 0; this.gpuWeights = new int[values.length]; for(int i=0;i<values.length;i++) { String value = values[i].trim(); if (value.length() <= 0) { this.gpuWeights[i] = -1; continue; } int weight = -1; try { weight = Integer.parseInt(value); if (weight < 0) weight = 0; } catch(Exception e) { } this.gpuWeights[i] = weight; if (weight > maxWeight) maxWeight = weight; } this.gpuWeightScale = maxWeight; for(int i=0;i<this.gpuWeights.length;i++) { if (this.gpuWeights[i] < 0) this.gpuWeights[i] = this.gpuWeightScale; } } WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".init: weightFactorEncode:"+weightFactorEncode+" weightFactorDecode:"+weightFactorDecode+" weightFactorScale:"+weightFactorScale); } @Override public void onTranscoderSessionCreate(LiveStreamTranscoder liveStreamTranscoder) { } @Override public void onTranscoderSessionInit(LiveStreamTranscoder liveStreamTranscoder) { } @Override public void onTranscoderSessionDestroy(LiveStreamTranscoder liveStreamTranscoder) { if (this.countGPU > 1) { WMSProperties props = liveStreamTranscoder.getProperties(); Object sessionInfoObj = props.get(PROPNAME_TRANSCODER_SESSION); if (sessionInfoObj != null && sessionInfoObj instanceof SessionInfo) { SessionInfo sessionInfo = (SessionInfo)sessionInfoObj; if (sessionInfo.gpuid < gpuInfos.length) { synchronized(this.lock) { gpuInfos[sessionInfo.gpuid].currentLoad -= sessionInfo.load; sessionInfo.load = 0; } WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".onTranscoderSessionDestroy["+liveStreamTranscoder.getContextStr()+"]: Removing GPU session: gpuid:"+sessionInfo.gpuid+" load:"+sessionInfo.load); } } } } @Override public void onHardwareInspection(TranscoderContextServer transcoderContextServer) { //{"infoCUDA":{"availabe":true,"availableFlags":65651,"countGPU":1,"driverVersion":368.81,"cudaVersion":8000,"isCUDAOldH264WindowsAvailable":false,"gpuInfo":[{"name":"GeForce GTX 960M","versionMajor":5,"versionMinor":0,"clockRate":1097500,"multiprocessorCount":5,"totalMemory":2147483648,"coreCount":640,"isCUDANVCUVIDAvailable":true,"isCUDAH264EncodeAvailable":true,"isCUDAH265EncodeAvailable":false,"getCUDANVENCVersion":5}]},"infoQuickSync":{"availabe":true,"availableFlags":537,"versionMajor":1,"versionMinor":19,"isQuickSyncH264EncodeAvailable":true,"isQuickSyncH265EncodeAvailable":true,"isQuickSyncVP8EncodeAvailable":false,"isQuickSyncVP9EncodeAvailable":false,"isQuickSyncH264DecodeAvailable":true,"isQuickSyncH265DecodeAvailable":false,"isQuickSyncMP2DecodeAvailable":true,"isQuickSyncVP8DecodeAvailable":false,"isQuickSyncVP9DecodeAvailable":false},"infoVAAPI":{"available":false},"infoX264":{"available":false},"infoX265":{"available":false}} boolean available = false; int countGPU = 0; String jsonStr = transcoderContextServer.getHardwareInfoJSON(); if (jsonStr != null) { try { JSON jsonData = new JSON(jsonStr); if (jsonData != null) { Map<String, Object> entries = jsonData.getEntrys(); Map<String, Object> infoCUDA = (Map<String, Object>)entries.get("infoCUDA"); if (infoCUDA != null) { Object availableObj = infoCUDA.get("availabe"); if (availableObj != null && availableObj instanceof Boolean) { available = ((Boolean)availableObj).booleanValue(); } if (available) { Object countGPUObj = infoCUDA.get("countGPU"); if (countGPUObj != null && countGPUObj instanceof Integer) { countGPU = ((Integer)countGPUObj).intValue(); } } } } } catch(Exception e) { WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".onHardwareInspection: Parsing JSON: ", e); } } WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".onHardwareInspection: CUDA available:"+available+" countGPU:"+countGPU); synchronized(lock) { this.available = available; this.countGPU = countGPU; if (this.countGPU > 1) { this.gpuInfos = new GPUInfo[this.countGPU]; for(int i=0;i<this.gpuInfos.length;i++) { this.gpuInfos[i] = new GPUInfo(); this.gpuInfos[i].gpuid = i; if (this.gpuWeights != null && i < this.gpuWeights.length) this.gpuInfos[i].weight = this.gpuWeights[i]; else this.gpuInfos[i].weight = gpuWeightScale; } } } } @Override public void onTranscoderSessionLoadBalance(LiveStreamTranscoder liveStreamTranscoder) { try { while(true) { if (this.gpuInfos == null) break; TranscoderStream transcoderStream = liveStreamTranscoder.getTranscodingStream(); if (transcoderStream == null) break; TranscoderSession transcoderSession = liveStreamTranscoder.getTranscodingSession(); if (transcoderSession == null) break; TranscoderSessionVideo transcoderSessionVideo = transcoderSession.getSessionVideo(); if (transcoderSessionVideo == null) break; MediaCodecInfoVideo codecInfoVideo = null; if (transcoderSessionVideo.getCodecInfo() != null) codecInfoVideo = transcoderSession.getSessionVideo().getCodecInfo(); long loadDecode = 0; long loadScale = 0; long loadEncode = 0; boolean isScalerCUDA = false; TranscoderStreamSourceVideo transcoderStreamSourceVideo = null; TranscoderStreamScaler transcoderStreamScaler = null; TranscoderStreamSource transcoderStreamSource = transcoderStream.getSource(); if (transcoderStreamSource != null) { transcoderStreamSourceVideo = transcoderStreamSource.getVideo(); if (transcoderStreamSourceVideo != null && codecInfoVideo != null && (transcoderStreamSourceVideo.isImplementationNVCUVID() || transcoderStreamSourceVideo.isImplementationCUDA())) { loadDecode = codecInfoVideo.getFrameWidth() * codecInfoVideo.getFrameHeight(); } else transcoderStreamSourceVideo = null; } transcoderStreamScaler = transcoderStream.getScaler(); if (transcoderStreamScaler != null) { isScalerCUDA = transcoderStreamScaler.isImplementationCUDA(); } List<TranscoderStreamDestination> destinations = transcoderStream.getDestinations(); if (destinations == null) break; for(TranscoderStreamDestination destination : destinations) { if (!destination.isEnable()) continue; TranscoderStreamDestinationVideo destinationVideo = destination.getVideo(); if (destinationVideo == null) continue; if (destinationVideo.isPassThrough() || destinationVideo.isDisable()) continue; TranscoderVideoFrameSizeHolder frameSizeHolder = destinationVideo.getFrameSizeHolder(); if (frameSizeHolder == null) continue; if (isScalerCUDA) loadScale += frameSizeHolder.getActualWidth() * frameSizeHolder.getActualHeight(); if (destinationVideo.isImplementationNVENC() || destinationVideo.isImplementationCUDA()) loadEncode += frameSizeHolder.getActualWidth() * frameSizeHolder.getActualHeight(); } long totalLoad = (loadDecode*weightFactorDecode) + (loadScale*weightFactorScale) + (loadEncode*weightFactorEncode); if (totalLoad <= 0) break; totalLoad /= LOAD_MAG; if (totalLoad <= 0) totalLoad = 1; int gpuid = -1; synchronized(lock) { long leastLoad = Long.MAX_VALUE; for(int i=0;i<gpuInfos.length;i++) { if (gpuInfos[i].getWeightedLoad() < leastLoad) { leastLoad = gpuInfos[i].getWeightedLoad(); gpuid = i; } } if (gpuid >= 0) gpuInfos[gpuid].currentLoad += totalLoad; } WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".onTranscoderSessionLoadBalance["+liveStreamTranscoder.getContextStr()+"]: gpuid:"+gpuid+" load:"+totalLoad+" [decode:"+loadDecode+" scale:"+loadScale+" encode:"+loadEncode+"]"); if (gpuid >= 0) { liveStreamTranscoder.getProperties().put(PROPNAME_TRANSCODER_SESSION, new SessionInfo(gpuid, totalLoad)); if (transcoderStreamSourceVideo != null) transcoderStreamSourceVideo.setGPUID(gpuid); if (transcoderStreamScaler != null && isScalerCUDA) transcoderStreamScaler.setGPUID(gpuid); for(TranscoderStreamDestination destination : destinations) { if (!destination.isEnable()) continue; TranscoderStreamDestinationVideo destinationVideo = destination.getVideo(); if (destinationVideo == null) continue; if (destinationVideo.isPassThrough() || destinationVideo.isDisable()) continue; if (destinationVideo.isImplementationNVENC() || destinationVideo.isImplementationCUDA()) destinationVideo.setGPUID(gpuid); } } break; } } catch(Exception e) { WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".onTranscoderSessionLoadBalance: Parsing JSON: ", e); } } }
不同性能的多个GPU之间的负载均衡
这个内建的TranscoderVideoLoadBalancerCUDASimple class 支持在不同性能的多个GPU之间实现负载均衡。你可以为每一个GPU设置不同的性能权重(或者叫负载权重,因为性能越高的当然可以承担更多的负载任务)。 这个权重配置在transcoderVideoLoadBalancerCUDASimpleGPUWeights参数中。
这个参数在一个列表中,用逗号分隔各个GPU的不同权重。我们建议你将性能最好的GPU的权重设置为100,然后其它性能低的GPU根据具体性能设置为对应的百分比。
对于这个列表中的GPU权重的顺序,你可以在Wowza Streaming Engine的启动日志中看到,也就是说这里的顺序和日志中显示的GPU顺序是一样的。 例如,如果你的服务器上有一个M5000 卡 (顺序 0) 和一个 M2000 卡 (顺序 1),那么在transcoderVideoLoadBalancerCUDASimpleGPUWeights中的权重可以按如下来配置:
<Property> <Name>transcoderVideoLoadBalancerCUDASimpleGPUWeights</Name> <Value>100,66</Value> </Property>这表示了M2000卡的性能只有M5000卡性能的66%。