-
中奖结果公示感谢各位小伙伴参与本次活动,本次活动获奖名单如下:请各位获奖的伙伴在8月6日之前点击此处填写收货地址,如逾期未填写视为弃奖。再次感谢各位小伙伴参与本次活动,欢迎关注华为云DTSE Tech Talk 技术直播更多活动~直播简介【直播主题】数仓实时入库利器!HStore表原理与应用实践详解【直播时间】2023年8月2日 16:30-18:00【直播专家】马俊松 华为云EI DTSE技术布道师【直播简介】面对实时入库和实时查询要求越来越高的趋势,已有的列存储无法支持并发更新入库,行存查询性能无法做到实时返回且空间压缩表现不佳。GaussDB(DWS)基于列存储格式设计和实现了全新的HStore表,同时提供高效的并发插入、更新入库,以及高性能实时查询。直播链接:cid:link_2活动介绍【互动方式】直播前您可以在本帖留下您疑惑的问题,专家会在直播时为您解答。直播后您可以继续在本帖留言,与专家互动交流。我们会在全部活动结束后对参与互动的用户进行评选。【活动时间】即日起—2023年8月3日【奖励说明】评奖规则:活动1:直播期间在直播间提出与直播内容相关的问题,对专家评选为优质问题的开发者进行奖励。奖品:华为云定制卫衣活动2:在本帖提出与直播内容相关的问题,由专家在所有互动贴中选出最优问题贴的开发者进行奖励。奖品:华为云定制Polo衫更多直播活动直播互动有礼:官网直播间发口令“华为云 DTSE”抽华为云定制棒球帽、填写问卷抽华为云定制飞盘等好礼;直播过程中提问,评选优质问题送华为云定制长袖卫衣。分享问卷有礼 :邀请5位朋友以上完成问卷即可获得华为云定制帆布袋。戳我填问卷》》老观众专属福利:连续报名并观看DTT直播3期以上抽送华为云DTT定制T恤。【注意事项】1、所有参与活动的问题,如发现为复用他人内容,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2023年8月4日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、其他事宜请参考【华为云社区常规活动规则】。
-
explain结果每个字段的含义说明https://bbs.huaweicloud.com/forum/thread-0212125114651587001-1-1.htmlmysql使用乐观锁示例https://bbs.huaweicloud.com/forum/thread-0212125200676611005-1-1.htmlmysql使用悲观锁示例https://bbs.huaweicloud.com/forum/thread-0205125201186136006-1-1.html查询表时候报:查看表数据时出错。https://bbs.huaweicloud.com/forum/thread-0275124625774236003-1-1.html数据库集群部署完毕后,应该调整哪些参数?https://bbs.huaweicloud.com/forum/thread-0228123756173382004-1-1.htmlgaussdb(DWS)更新整表后表占用空间翻倍如何解决https://bbs.huaweicloud.com/forum/thread-0206123929068130003-1-1.htmlMysql写热点分散优化https://bbs.huaweicloud.com/forum/thread-0212125802476751036-1-1.htmlMySQL:分库分表与分区的区别和思考https://bbs.huaweicloud.com/forum/thread-0227125728343611018-1-1.htmlMySQL索引优化20招https://bbs.huaweicloud.com/forum/thread-0212125728273875028-1-1.htmlGaussDB技术解读系列之高级压缩https://bbs.huaweicloud.com/forum/thread-0249125114778677001-1-1.htmlGaussDB(for Redis)企业级特性揭秘之多租户管理https://bbs.huaweicloud.com/forum/thread-0222125638395491011-1-1.htmlMyBatis的xml传参方式https://bbs.huaweicloud.com/forum/thread-0212125743779350032-1-1.htmlJava中单例模式的实现方式https://bbs.huaweicloud.com/forum/thread-0212125729247074029-1-1.html 引入RabbitMQ后,你如何保证全链路数据100%不丢失?https://bbs.huaweicloud.com/forum/thread-0249125644459392020-1-1.html浅析内存屏障以及在java中的应用https://bbs.huaweicloud.com/forum/thread-0227125555524364010-1-1.html什么是JAVA动态代理?两种常用的动态代理方式https://bbs.huaweicloud.com/forum/thread-0227125565964720011-1-1.htmlExplain详解https://bbs.huaweicloud.com/forum/thread-0212125975692823053-1-1.htmlvacuum full提示 WARNING: TopSQL lfq is full, failed to save queryid: xxxhttps://bbs.huaweicloud.com/forum/thread-0227125996961619001-1-1.html
-
中奖结果公示感谢各位小伙伴参与本次活动,本次活动获奖名单如下:请各位获奖的伙伴在7月25日之前点击此处填写收货地址,如逾期未填写视为弃奖。再次感谢各位小伙伴参与本次活动,欢迎关注华为云DTSE Tech Talk 技术直播更多活动~直播简介【直播主题】揭秘openGemini时序数据库应用场景与技术实践【直播时间】2023年7月19日 16:30-18:00【直播专家】向宇 华为云开源DTSE技术布道师;openGemini社区发起人【直播简介】本期直播由向宇详细讲解华为云开源的openGemini高性能、分布式时序数据库,将围绕场景、案例、架构、功能、性能、开发、成本等方面,帮助开发者更容易、更清晰的了解openGemini是什么,要解决什么问题,提供哪些能力,差异化竞争力是什么,性能如何,如何使用(安装部署、应用开发等)以及如何运维等一系列问题。直播链接:cid:link_2活动介绍【互动方式】直播前您可以在本帖留下您疑惑的问题,专家会在直播时为您解答。直播后您可以继续在本帖留言,与专家互动交流。我们会在全部活动结束后对参与互动的用户进行评选。【活动时间】即日起—2023年7月20日【奖励说明】评奖规则:活动1:直播期间在直播间提出与直播内容相关的问题,对专家评选为优质问题的开发者进行奖励。奖品:华为云定制长袖卫衣活动2:在本帖提出与直播内容相关的问题,由专家在所有互动贴中选出最优问题贴的开发者进行奖励。奖品:华为云定制飞盘更多直播活动直播互动有礼:官网直播间发口令“华为云 DTSE”抽华为云云宝公仔、填写问卷抽华为云定制鼠标垫等好礼;直播过程中提问,评选优质问题送华为云定制长袖卫衣。分享问卷有礼 :邀请5位朋友以上完成问卷即可获得华为云定制棒球帽。戳我填问卷》》老观众专属福利:连续报名并观看DTT直播3期以上抽送华为云DTT定制T恤。【注意事项】1、所有参与活动的问题,如发现为复用他人内容,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2023年7月21日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、其他事宜请参考【华为云社区常规活动规则】。
-
1.绑定(Bindings)注射器(injector)的主要作用是搜集对象依赖图。当我们需要创建一个类型的对象时,他就会找出绑定类,然后会解决依赖的关系,并将所有的关系联系到一起。为了帮助解决依赖之间的关系,我们需要给injector指定绑定关系。1.1 创建绑定关系创建bind,我们只需要实现AbstractModule接口,然后重写configure方法即可。在这个方法体中,我们通过调用bind方法就可以实现绑定了。这些方法会进行类型检查,所以当我们参数有问题的时候,编译就会报错。我们创建了自定义的module之后就可以将其作为参数传递给Guice.creatInjector()来创建一个injector了。绑定方式也有很多种:linked bindingsinstance bindings@Provides methodsprovider bindingsconstructor bindingsuntargetted bindings1.2 其他的绑定除了普通的绑定之外,我们还可以指定内嵌(built-in)绑定的injector。当有一个创建请求到达,但是为找到依赖项时,它就会尝试创建一个即时绑定(just-in-time)。2. Linked bindingsLinked Bindings 会将一种类型与它的实现绑定。下面的例子,就是将TransactionLog接口绑定到了它的实现类 DatabaseTransactionLog上了:public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); }}如此,当我们调用injector.getInstance(TransactionLog.class)时,或者有类依赖于TransactionLog时,它就会创建DatabaseTransactionLog。它就是把一种类型绑定到了它的子类或者实现类上。我们甚至可以把这个实现类DatabaseTransactionLog绑定到它的子类上:bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);Link bindings 还可以传递绑定关系,就是连在一起,如下:public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class); }}在上面的代码中,当我们需要一个TransactionLog的时候,injector就会返回一个MySqlDatabaseTransactionLog对象。3. Bindings Annotations通常情况下,我们的一种类型可能绑定了多个实现或者子类。例如,当你需要使用信用卡消费时,你用的可能是PayPal的卡,也可能是Google的卡。在这种情况下你就需要使用不同的processor 实现类了。为了做到这一点,guice支持注解绑定。注解加上类型组合在一起就是唯一的标识符了。这部分称之为key。自定义注解定义一个绑定注解,需要在代码中导入相应的库,当然现在的IDE都有自动导入功能了,这个应该没什么问题。package example.pizza;import com.google.inject.BindingAnnotation;import java.lang.annotation.Target;import java.lang.annotation.Retention;import static java.lang.annotation.RetentionPolicy.RUNTIME;import static java.lang.annotation.ElementType.PARAMETER;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.ElementType.METHOD;@BindingAnnotation@Target({ FIELD, PARAMETER, METHOD })@Retention(RUNTIME)public @interface PayPal {}我们没必要去关心这些元注解,但是如果你真的想要知道的话,可以看下面:@BindingAnnotation:这个注解是告诉Guice,这个是个绑定注解。这样,在后面如果这个类型没有对应多个绑定注解,那么Guice就会报错;@Target({FIELD, PARAMETER, METHOD}): 这个注解可以防止@PayPal在没用的地方被意外引用。@Retention(RUNTIME):让这个注解只在运行时有效。通过绑定注解,接下来我们就可以通过注解来注入参数了:public class RealBillingService implements BillingService { @Inject public RealBillingService(@PayPal CreditCardProcessor processor, TransactionLog transactionLog) { ... }最后我们通过注解把类型和对应的实现类绑定起来。在bind()方法后调用annotateWith(PayPal.class)即可,如下:bind(CreditCardProcessor.class) .annotatedWith(PayPal.class) .to(PayPalCreditCardProcessor.class);通过自定义注解实现绑定的过程主要有三步:定义注解绑定注解注解注入@Name注解Guice也提供了一个内嵌的注解@Name,这个注解通过一个字段指定绑定的实现类:public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... }在需要注入的地方我们直接通过Names.name就可以指定实现类了:bind(CreditCardProsssscessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);因为编译器无法对字符串做类型检查,所以使用这个Name注解可能不太安全。最好还是通过定义自定义注解来绑定对象,这样可以保证类型安全。4. Instance Bindings在Guice中我们可以直接将一种类型绑定到它的对象实例上。这个通常只用于那些自身没有依赖项的对象,例如某些比较特殊的值对象:bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);避免使用.toInstance创建复杂对象,这会导致我们的程序在启动时比较耗性能。可以使用@Provides方法代替这种方式。5. @Provides Methods当需要创建通过代码创建对象时,我们可以使用一个@Provides方法。这个方法必须在Module中绑定,而且必须使用@Provides注解。这个方法返回一个限定类型的对象。当注射器(Injector)需要创建一个这种类型的对象时,它就会调用这个方法。public class BillingModule extends AbstractModule { @Override protected void configure() { ... } @Provides TransactionLog provideTransactionLog() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza"); transactionLog.setThreadPoolSize(30); return transactionLog; }}我们也可以给这个方法添加自定义注解,或者@Named注解,这样我们就可以像使用注解绑定那样来使用这个方法创建对象了。我们也可以在这个方法中使用值对象绑定:@Provides @PayPal CreditCardProcessor providePayPalCreditCardProcessor( @Named("PayPal API key") String apiKey) { PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor(); processor.setApiKey(apiKey); return processor; }Provides方法不允许抛出异常,如果需要抛出异常可以考虑使用ThrowingProviders extension ——@CheckedProvides 方法 。6. Provider Bindings当我们需要使用很多的@Provides方法,而且它们的实现都比较复杂的时候,我们可能就需要考虑把它们各自迁移到一个特定的类中了。provider类需要实现Guice的Provider接口,然后实现一个简单的get方法:public interface Provider<T> { T get();}我们自己实现的provider类可以通过构造器上使用@Inject注解注入自己的依赖。下面的例子就是通过实现Provider接口,定义了一个返回复杂类型的方法:public class DatabaseTransactionLogProvider implements Provider<TransactionLog> { private final Connection connection; @Inject public DatabaseTransactionLogProvider(Connection connection) { this.connection = connection; } public TransactionLog get() { DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setConnection(connection); return transactionLog; }}最后我们通过.toProvider来绑定provider:public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class); }为了保证代码质量和功能,当我们的provide方法比较复杂的时候,最后保证测试通过。跟provide方法一样,Provider中的方法也不允许抛出异常,如果需要抛出异常可以考虑使用ThrowingProviders extension ——@CheckedProvides 方法 。7. Untargeted Bindings有些时候我可能没法把依赖绑定到一些具体的目标上,这个时候@ImplementedBy或者@ProvidedBy类型注解就比较有用了。非目标绑定会将绑定类型信的相关信息通知给injector,这样的绑定不会使用to方法,具体的使用方法如下:bind(MyConcreteClass.class); //通知injector这是一个单例对象 bind(AnotherConcreteClass.class).in(Singleton.class);然后在使用注解绑定的时候,我们再将其绑定到具体的实现类上:bind(MyConcreteClass.class) .annotatedWith(Names.named("foo")) .to(MyConcreteClass.class); bind(AnotherConcreteClass.class) .annotatedWith(Names.named("foo")) .to(AnotherConcreteClass.class) .in(Singleton.class);8. Constructor BindingsGuice 3.0版本新特性有些时候我们需要把一种类型绑定到任意的构造器上。这个通常出现在@Inject注解无法应用于目标构造器的情况:要么因为它是一个第三方类,要么是因为它有多个构造器参与了依赖注入。@Provides方法提供了最佳的解决方案。通过显式调用我们自己的对象构造方法,我们就可以得到相应的类型了。但是这种方法有个缺陷:手动的构造的对象无法参与到AOP中。为了解决这个问题,Guice提供了toConstructor()绑定的方式。这种方式需要我们通过反射选择一个类的构造器。而且,当构造器不存在的时候我们需要自己解决异常:public class BillingModule extends AbstractModule { @Override protected void configure() { try { bind(TransactionLog.class).toConstructor( DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class)); } catch (NoSuchMethodException e) { addError(e); } }}在这个例子中,DatabaseTransactionLog 类应该得有一个只接受一个DatabaseConnection类型参数的构造器。这个构造器不需要@Inject注解。Guice会调用它的构造器来适配对应的绑定。每个toConstructor()绑定都是独立的。如果我们将一种单例类型绑定在多个构造器上,那么每个构造器都是创建出它们对应的对象。9. Bult-in Bindings除了显示绑定和即时绑定之外,Guice也内嵌了一些其他的绑定。LoggersGuice专门为java.util.logging.Logger内嵌了一个绑定,这样可以节省一些我们的开发时间。这个绑定会自动把logger的名字设置成logger被注入的类名。@Singletonpublic class ConsoleTransactionLog implements TransactionLog { private final Logger logger; @Inject public ConsoleTransactionLog(Logger logger) { this.logger = logger; } public void logConnectException(UnreachableException e) { /* the message is logged to the "ConsoleTransacitonLog" logger */ logger.warning("Connect exception failed, " + e.getMessage()); }Injector在我们的框架代码中,有的时候直到代码运行时我们才知道哪一种类型是我们需要的。在这种特殊情况下,我们就需要注入injector了。需要注意的是,注入injector的代码不会自行记录它的依赖关系,所以我们需要谨慎地使用这种方式。ProvidersGuice可以为所有感知到类型注入一个Provider。在Injectiong Providers中可以了解实现的细节。TypeLiteralsGuice对其注入的所有类型都具有完整的类型信息。如果我们需要参数化这些类型,我们可以注入一个TypeLiteral<T>。这样我们就可以通过一些反射机制获取这些元素的类型了。The StageGuice支持不同的阶段枚举类型,这样就可以区分开发环境和生产环境了。MembersInjectors当我们绑定providers或者扩展一些extensions的时候,我们可能需要Guice为我们写的对象注入依赖。想要做到这一点,只要添加一个MemberInjector<T>(其中T是我们自己的对象类型),然后通过调用membersInjector.injectMembers(myNewObject)就可以了。10. Just-in-time Bindings当injector需要一种类型的对象实例时,它就需要指定绑定。在modules中的绑定,我们称之为显式绑定,而我们想用的时候就可以通过这些modules创建injector。当我们需要的一个类型没有通过显示绑定到实现类上时,injector就会尝试创建一个即使绑定(Just-In_Time buding),这也被称之为JIT绑定或隐式绑定。Eligible ConstructorGuice可以通过类型可注入的构造器为具体的类型穿件绑定。这个要么是一个无私有字段的空参构造器,要么是一个有@Inject注解的构造器:public class PayPalCreditCardProcessor implements CreditCardProcessor { private final String apiKey; @Inject public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) { this.apiKey = apiKey; }一些内嵌的类,除非它们static修饰的,否则Guice不会构造这些类。内部类对其无法注入的封闭类有隐式引用。@ImplementedBy这个注解可以告诉injector,接口默认的实现类是哪个。@ImplementedBy就跟Link binding差不多,当我们没有通过其他方式指定和注入的时候,默认注入的就是这个注解指定的实现类:@ImplementedBy(PayPalCreditCardProcessor.class)public interface CreditCardProcessor { ChargeResult charge(String amount, CreditCard creditCard) throws UnreachableException;}上面这个实现通过Link binding操作就是:bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);如果一个类型同时使用了bind()方法和@ImplementedBy注解。那么注解指定的默认实现类会被bind()方法指定的实现类覆盖。我们在使用@ImplementedBy的时候还是需要小心一点的;它为接口添加了一个运行时的依赖绑定。@ProvidedBy@ProvidedBy可以告诉injector生产实例对象的Provider:@ProvidedBy(DatabaseTransactionLogProvider.class)public interface TransactionLog { void logConnectException(UnreachableException e); void logChargeResult(ChargeResult result);}这个跟.toProvider()效果是一样的:bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class);跟@ImplementedBy一样,当我们同时使用注解和方法绑定时,注解会被方法覆盖掉。转自:https://zhuanlan.zhihu.com/p/96106251
-
翻译自官方文档,能力有限,如有缺漏,还望指正。把所有的代码都糅合到一起,这可能是开发过程中最让人觉得无聊和枯燥的事。当然,我们有很多的方式可以把数据、类、接口和服务联系起来。现在我们就通过一段代码来展示一下不同方法的不同之处。假设我们现在有个需求,是为披萨店处理网上订单的,下面我们就可以声明一个接口服务,然后通过具体的类来实现它。public interface BillingService { /** * Attempts to charge the order to the credit card. Both successful and * failed transactions will be recorded. * * @return a receipt of the transaction. If the charge was successful, the * receipt will be successful. Otherwise, the receipt will contain a * decline note describing why the charge failed. */ Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);}这个接口只有一个方法,支付订单。然后为了测试这个接口,我们需要实现它。下面通过不同的方法实现这一功能。直接调用构造器这里我们是直接创建(new)了一个CreditCardProcessor来TransactionLog处理支付:public class RealBillingService implements BillingService { public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { CreditCardProcessor processor = new PaypalCreditCardProcessor(); TransactionLog transactionLog = new DatabaseTransactionLog(); try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } }}这段代码就暴露了一些对于模块化和单测不友好的问题。这个方法是依赖一个真实的PaypalCreditCardProcessor(真的能刷你钱的那种)。那么在编写这个测试类的时候,开发人员如果需要测试这个功能的话,就必须要用真实的信用卡来支付,否则没法判断服务是否可用。工厂工厂类可以让客户端和实现类解耦。一般的工厂都会通过一个静态方法来获取和设置接口的实现类。工厂方法和工厂类,不仅仅可以让某些繁琐的对象创建过程变得可复用,更是使对象创建的过程有了更多的可能性。下面是一个简单工厂的样例代码:public class CreditCardProcessorFactory { private static CreditCardProcessor instance; public static void setInstance(CreditCardProcessor processor) { instance = processor; } public static CreditCardProcessor getInstance() { if (instance == null) { return new SquareCreditCardProcessor(); } return instance; }}可以看到,上述代码中,工厂方法并不依赖于具体的CreditCardProcessor类,即没有创建一个实例。而客户端由可以通过setInstance方法来传递这个对象。对原有的功能没有影响,却可以大大地扩展程序的灵活性。接下来改写客户端代码 ,让它没有new的操作,这样即使没有创建真正的CreditCardProcessor对象,工厂也会返回一个自己创建的用于测试的CreditCardProcessor类型的对象回来。public class RealBillingService implements BillingService { public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { CreditCardProcessor processor = CreditCardProcessorFactory.getInstance(); TransactionLog transactionLog = TransactionLogFactory.getInstance(); try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } }}这样单测就自然而然地写出来了:public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100); private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog(); private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); @Override public void setUp() { TransactionLogFactory.setInstance(transactionLog); CreditCardProcessorFactory.setInstance(processor); } @Override public void tearDown() { TransactionLogFactory.setInstance(null); CreditCardProcessorFactory.setInstance(null); } public void testSuccessfulCharge() { RealBillingService billingService = new RealBillingService(); Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge()); assertEquals(100, receipt.getAmountOfCharge()); assertEquals(creditCard, processor.getCardOfOnlyCharge()); assertEquals(100, processor.getAmountOfOnlyCharge()); assertTrue(transactionLog.wasSuccessLogged()); }}但是,这会有另外一个问题。以为工厂类中需要返回的是一个全局的静态变量。也就以为在我们的测试类中需要在setUp方法中给他赋值,然后在tearDown方法中将这些变量注销掉。如果因为某些原因导致这个tearDown方法没有执行,那么这个全局的变量就不会被注销,这样在并行执行测试类的方法时就有可能让其他的测试类没法通过。而且,更大的问题是类之间的依赖关系被隐藏在了工厂代码之中,如果我们添加了新的依赖,则必须要重跑测试了。在这一过程中,我们要是没有初始化工厂的话,则代码只有运行到这一步的时候,我们才会知道。随着我们的业务的扩大,这种压力也会越来越大。代码质量问题,可以被质量监管或者完善的测试更正。但是我们可以做的更好!我们稍微暂停一下整理整理思路。从一开始对象在方法中创建,到后面对象在工厂中创建,通过工厂和静态变量传递到方法中。我们一直改变的只是对象的获取途径。在这一前提下,我们是否可以考虑更多的可能呢?依赖注入同工厂相似,依赖注入也是一种设计模式。其核心的原则是将创建行为从依赖解决中分离出来。在下面的例子中,RealBillingService并不负责TransactionLog 和 CreditCardProcessor对象的创建。他们是作为构造器参数被传递进来的:public class RealBillingService implements BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } }}这样我们即使不需要过工厂,也能够自己创建需要processor来完成支付的动作了。public class RealBillingServiceTest extends TestCase { private final PizzaOrder order = new PizzaOrder(100); private final CreditCard creditCard = new CreditCard("1234", 11, 2010); private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog(); private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor(); public void testSuccessfulCharge() { RealBillingService billingService = new RealBillingService(processor, transactionLog); Receipt receipt = billingService.chargeOrder(order, creditCard); assertTrue(receipt.hasSuccessfulCharge()); assertEquals(100, receipt.getAmountOfCharge()); assertEquals(creditCard, processor.getCardOfOnlyCharge()); assertEquals(100, processor.getAmountOfOnlyCharge()); assertTrue(transactionLog.wasSuccessLogged()); }}现在,如果我们移除依赖的话,代码是不会通过编译的。依赖在接口的签名中就已经暴露出来了。 这一种方式就是依赖注入了,类依赖的对象实例在需要的时候通过构造器或者其他的方法注入进来。但是不尽如人意的是,现在BillingService的客户端需要自己管理这些依赖了。当然,我们可以再使用模式来解决这个问题。依赖于这些的类,可以把BllingService作为一个参数传递到它们的构造器之中。此时,如果没有一个好的框架,我们最终还是要在顶层创建这个类,然后一层一层地往下传递,注入。public static void main(String[] args) { CreditCardProcessor processor = new PaypalCreditCardProcessor(); TransactionLog transactionLog = new DatabaseTransactionLog(); BillingService billingService = new RealBillingService(processor, transactionLog); ... }使用Guice完成依赖注入Google的Guice框架可以简单的完成代码中的依赖注入,而且,通过这个框架写出的代码可以实现模块化和测试。在我们的支付demo中使用Guice,第一件是要确定接口和实现类的绑定关系。这个可以通过实现Guice的Module接口来完成:public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class); bind(BillingService.class).to(RealBillingService.class); }}上面通过模块配置,将接口和实现类绑定在了一起。然后在RealBillingService的构造器上加上@Inject注解,这样Guice就会在需要时,为每个参数注入相应的值。当然,前提这些参数是通过配置绑定过了。public class RealBillingService implements BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; @Inject public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { try { ChargeResult result = processor.charge(creditCard, order.getAmount()); transactionLog.logChargeResult(result); return result.wasSuccessful() ? Receipt.forSuccessfulCharge(order.getAmount()) : Receipt.forDeclinedCharge(result.getDeclineMessage()); } catch (UnreachableException e) { transactionLog.logConnectException(e); return Receipt.forSystemFailure(e.getMessage()); } }}最后,把这些东西放到一起,这样在任何需要这些依赖类的地方我们就可以后获取到相应的对象了。public static void main(String[] args) { Injector injector = Guice.createInjector(new BillingModule()); BillingService billingService = injector.getInstance(BillingService.class); ... }如何实现的?在上面的这种方式中,对象通过注入接收了在构造器中的对象。为了构造一个对象,我们首先要确定的就是它所依赖对象的绑定关系,即他需要的是什么对象。要知道一个接口的实现类可能有很多很多个。但是如果要绑定一个依赖的话,我们可能还需要绑定这些依赖的依赖,这样一直下去,我们就需要维护一个对象依赖图了。人工创建一个对象依赖图是相当费劲的,而且容易出错,导致测试困难。当然,如果使用Guice的话,这个对象依赖图就不需要你来创建了,它会帮你做。但是首先我们得配置Guice,让它按照我们所希望的方式来构建对象依赖图。举个例子,在BillingService 类中,我们依赖 CreditCardProcessor 和 TransactionLog。 为了让这个类的构造器能够被Guice调用,我们需要使用@Inject注解:class BillingService { private final CreditCardProcessor processor; private final TransactionLog transactionLog; @Inject BillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) { ... }}这个注解就告诉了Guice,构造器依赖PaypalCreditCardProcessor和DatabaseTransactionLog 对象需要注入进来。之后,Guice就会通过绑定关系来注入他们的实现类。Module就是一个绑定关系的集合。其绑定的方式通过方法调用很容易理解,就跟自然语言的表达方式类似:public class BillingModule extends AbstractModule { @Override protected void configure() { /* * This tells Guice that whenever it sees a dependency on a TransactionLog, * it should satisfy the dependency using a DatabaseTransactionLog. */ bind(TransactionLog.class).to(DatabaseTransactionLog.class); /* * Similarly, this binding tells Guice that when CreditCardProcessor is used in * a dependency, that should be satisfied with a PaypalCreditCardProcessor. */ bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class); }}不同的module为injector(injector)指定了不同的绑定关系。在获取对象的时候,我们需要先创建一个injecotr,之后我们就可以使用它来获取我们想要的对象了。public static void main(String[] args) { /* * Guice.createInjector() takes your Modules, and returns a new Injector * instance. Most applications will call this method exactly once, in their * main() method. */ Injector injector = Guice.createInjector(new BillingModule()); /* * Now that we've got the injector, we can build objects. */ BillingService billingService = injector.getInstance(BillingService.class); ... }通过上面的demo,我们已经创建了一个小小的对象依赖图。这个图包括了BillingService和它所依赖的CreditCardProcessor,TransactionLog转自:https://zhuanlan.zhihu.com/p/96106067
-
前言:作为一个大型的开源项目,其设计模式,代码架构和设计理念中必然有值得我们学习和了解的内容。同时作为一个成千上万开源contributor参与的开源项目,其代码风格和编程习惯上肯定有会有所差异。分析这样的项目,必然要从宏观上了解其代码逻辑的基本流程,从细微处学习其常用的模式和编程方法。今天就看看Druid是如何将Guice依赖注入运用在自己的代码中,又为什么要这样去实现? 我们以CliMiddleManager为入口,这个类比较简单只有一个从写的方法——getModules,返回Guice的配置信息。这个在之前的Guice文章中有说过它的作用,这里不再说明。那么这个getModules又是如何被使用的呢?其实这个方法是在它的父类GuiceRunnable中声明的从上图我们可以看到,其实Druid的每个服务继承了这个抽象类。这里实际上是用到了一个模板方法模式——父类定义算法流程,算法的实现细节由子类决定。在父类GuiceRunnable的makeInjector方法中会使用到这个getModules方法返回的modules来创建一个injector。而具体的绑定配置则可以通过子类实现的getModules来进行配置。在这个方法中,每个服务都会将使用到的一些变量,依赖的其他类进行配置。 下面是makeInjector方法的具体实现 public Injector makeInjector() { try { return Initialization.makeInjectorWithModules( baseInjector, getModules() ); } catch (Exception e) { throw Throwables.propagate(e); } }这样一来,就通过客户端的方法重写,完成了指定配置。Guice的一般使用步骤是:1. 定义接口;2. 实现接口;3. 继承Module完成注入配置;4. 创建Injector对象;5. 注入实现类或者变量等。上面的流程实际上已经完成了1~4,接下来我们在看看它是在什么地方进行注入的。在上面的分析中,我们知道这几个类的顶级父类是Runnable,按照面向接口编程的思维,在后面是会调用run方法的,而run方法是在ServerRnnable中实现的,我们看一下其具体的逻辑:public void run() { final Injector injector = makeInjector(); final Lifecycle lifecycle = initLifecycle(injector); try { lifecycle.join(); } catch (Exception e) { throw Throwables.propagate(e); } }}我们可以看到,创建出来的Injector方法主要是被initLifecycle这个方法使用了。在这个方法中,上面的配置会被用来创建一个LifeCycle对象,通过这个对象来完成middleManger服务的启动。其实在这里我是有个疑问的,我们先看看这个三个类之间的关系:我的问题就是,为什么要重新定义一个ServerRunnable类来重写run方法,而不是在GuiceRunnable类或者CliMiddleManager类中重写。然后,我看了一下类的继承关系,发现了GuiceRunnable还被其他的一些类继承了,而他们run的实现逻辑是不同的,所以是不能在父类中是实现。 至于为什么不在CliMiddleManager中实现也很容易理解,这是为了代码复用,因为由相当一部分子类的run实现逻辑是相同的,抽出这一部分代码封装到单独的类中就可以减少重复代码。转自 https://zhuanlan.zhihu.com/p/96105772
-
前言QueryId作为Druid区分查询的唯一标识,是我们定位查询问题有效的输入。用户查询有问题时,优先提供QueryId,如果在日志中捞不到的话,我们再根据dataSource名,interval以及查询装态来判断是哪条查询。 Druid MetricsDruid 服务在运行时会将自身的一些metrics(指标信息)记录到日志文件中,也可以通过HTTP请求发送给其他的消息中间件,例如Apache Kafka。 默认情况下指标信息的记录是关闭的,需要在部署服务的时候手动开启。在我们的集群中,Druid的指标信息是通过kafka实时落入Druid的一个dataSource中druid_metrics。metrics记录的是全维度信息,通过service维度来区分服务,通过metric来取分指标,且指标对应的的指标值为value。dataSource的schema可以入Druid的时候进行更改。不同服务对应的指标可以在官网查看[Druid Metrics](http://druid.io/docs/latest/operations/metrics.html。需要注意的是,有些指标是需要配置了相应的Monitor之后才可以记录的,具体信息可以在官网查看。 SupersetSuperset 是一款由 Airbnb 开源的“现代化的企业级 BI(商业智能) Web 应用程序”,其通过创建和分享 dashboard,为数据分析提供了轻量级的数据查询和可视化方案。 Druid指标信息的可视化展示是通过Druid+Superset来实现的。Druid提供数据查询来源,而Superset则将结果通过报表或者其他的方式展示出来。这样我们可以很轻松地查看和分析Druid的很多指标信息,包括:查询指标,集群状态,服务负载等等。查询问题分析现在我们认为用户的唯一输入是queryId,则可以将其作为过滤条件筛选出相应的指标。确定查询语句首先,最重要的是我们要获取到用户的查询语句,而查询是请求Broker的,所以据提的查询我们可以在Broker的日志中获取。但是,如果有多个Broker,我们如何确定是哪个Broker?一个一个查可以,也可以通过druid_metrics查看。具体的查询语句如下: { "queryType":"topN", "dataSource":"druid_metrics", "dimension":"host", "threshold":10, "metric":"count", "intervals":["2019-06-01T14:30:00+08:00/2019-06-06T14:40:00+08:00"], "granularity":"all", "filter":{ "type":"and", "fields":[ { "type":"selector", "dimension":"id", "value":"8c891ec8-fc76-4711-9048-abd0cc2a459c" }, { "type":"selector", "dimension":"service", "value":"druid/broker" } ] }, "aggregations":[ { "type":"count", "name":"count" }]}查询结果如下: [ { "timestamp": "2019-06-04T07:00:00.015Z", "result": [ { "count": 9, "host": "test-test.com:8082" } ] }]接着在服务器上对应的broker_request.log日志中搜索即可,cat broker_request.log | grep ${queryId}。上述步骤可以确定查询语句。当然也可以在superset上完成,更加方便。加上对应的过滤条件,直接查询即可。superset是一个很方便好用的可视化工具,对于一些指标信息的展示非常的直观。关于如何使用superset,这里不做赘述。 各个节点查询耗时当然,有了查询语句我们不一定能够分析出问题所在。因为影响查询的因素有很多,查询语句只是一个方面。为了更加具体地分析问题,我们首先得清楚Druid查询的整条链路。Druid中的数据分为两部分:实时数据和历史数据。历史数据在Druid的historical节点上查询,而实时数据会在middleManager的实时任务(peon进程)中查询。 说完了数据类别,再根据上图来分析Druid查询的链路。首先客户端的查询来到broker,broker首先会去ZK上获取查询的timeline,即对应的数据在什么位置,historical节点上还是middleManager(Peon进程)上。确定之后,broker会把查询成子查询,路由给对应的节点,对应节点处理查询之后,返回结果给broker,broker再把查询结果合并返回给客户端。那么一次查询涉及到的Druid服务就有:Broker,Historical,middleManager上的peon进程。所以分析时,我们可以根据这入手。 我们先看看在各个节点上查询所耗费的时间和返回的数据量。具体的查询语句如下: { "queryType":"groupBy", "dataSource":"druid_metrics", "dimensions":["service","host"], "intervals":["2019-06-01T14:30:00+08:00/2019-06-06T14:40:00+08:00"], "granularity":"all", "filter":{ "type":"and", "fields":[ { "type":"selector", "dimension":"id", "value":"8c891ec8-fc76-4711-9048-abd0cc2a459c" }, { "type":"in", "dimension":"service", "values":["druid/broker","druid/historical","druid/middlemanager"] } ] }, "aggregations":[ { "type":"longMax", "name":"cost_time", "fieldName":"value_max" }]}查询结果如下: [ { "version": "v1", "timestamp": "2019-06-01T06:30:00.000Z", "event": { "host": "rantLing1-cool.com:8082", "service": "druid/broker", "cost_time": 4285 } }, { "version": "v1", "timestamp": "2019-06-01T06:30:00.000Z", "event": { "host": "rantLing2-cool.com:8083", "service": "druid/historical", "cost_time": 1081 } }, { "version": "v1", "timestamp": "2019-06-01T06:30:00.000Z", "event": { "host": "rantLing3-cool.com.com:8083", "service": "druid/historical", "cost_time": 1081 } }]这里我们可以看到查询在每个节点的耗时,如果有耗时很长的情况,我们可以通过zabbix查看当时机器的负载情况,是否是外部因素导致的查询问题。当然还有可能是查询本身的数据量就很大,涉及的segments过多导致的查询问题。 小结:查询出现了问题,例如超时,在集群正常服务的情况下。我确认问题的流程大致如下:1. 确认查询语句,返回的queryStatus中是否有明显的报错,例如返回数据量超过阈值等。如果没有继续排查; 2. 确认查询涉及到的数据量是不是过大,设计的segment是不是过多。例如一个interval内有10T的数据,这样涉及的segments数量就有可能过多了,查询耗时可能就比较长。如果没有问题在继续下一步;3. 查看各个节点耗时,是broker端耗时较长还是其他节点。如果是节点耗时较长可以通过一些机器监控的工具查看当时的机器负载。如果是负载有问题,可能就要跟相关人员确认是什么原因导致了。当然负载过大可能也是因为较大的查询导致的。这个也是要另行分析的。转自知乎:https://zhuanlan.zhihu.com/p/96105460
-
前言Druid Coordinator 服务在整个Druid架构中的主要作用是协调Segments在历史节点之间的分布。其主要功能包括是segments在historical之间分布均衡,rules管理,segments清理合并等等。 源码分析Druid 版本: druid-0.12.3DruidCoordinatorDruidCoordinator是Coordinator服务的入口,Coordinator启动的流程都是在这个类中开始。首先看一下它的内部类:DruidCoordinator 一共有三个内部类,即使不看类的定义,我们从类名也可以知道这三个内部类都实现了Runnable接口。在Coordiantor服务中应该是作为三个独立的线程异步执行。我们先大致了解一下每个Runnable的完成的工作。 CoordinatorRunnable 查看这个类的源码,除了一些判断结构之外,其最主要的实现如下: // Do coordinator stuff. DruidCoordinatorRuntimeParams params = DruidCoordinatorRuntimeParams.newBuilder() .withStartTime(startTime) .withDatasources(metadataSegmentManager.getInventory()) .withDynamicConfigs(getDynamicConfigs()) .withEmitter(emitter) .withBalancerStrategy(balancerStrategy) .build(); for (DruidCoordinatorHelper helper : helpers) { // Don't read state and run state in the same helper otherwise racy conditions may exist if (coordLeaderSelector.isLeader() && startingLeaderCounter == coordLeaderSelector.localTerm()) { params = helper.run(params); } }在这里,CoordinatorRunnable首先组装出了一个原始的DruidCoordinatorRuntimeParams 对象(后用params代替),遍历执行了helper中的run方法。这里是使用了装饰器模式,run方法返回的依然是一个params对象,只是对象又进行了一波加工。查看helpers的定义,知道了这是一个List<DruidCoordinatorHelper>。该对象的初始化是通过构造器来完成的。想要明白helper做了什么,就需要找到这个类在什么地方被使用,对象是如何创建的。查看类图,如下:CoordinatorRunnable作为一个抽象类,在工程中并没有方法直接创建CoordinatorRunnable对象,也就是说他的run方法是通过子类调用的。有意思的是,CoordinatorRunnable被声明成一个抽象类,但是在它的类定义中并没有声明抽象方法。我的印象中,抽象类多用于模板方法模式。但是乍一看这个类并没有使用这一种模式,唯一一个没有确定的方法就是helper的run方法。也就是说,什么样的helper决定了run方法做了什么样的事。helper是通过构造器创建的,而CoordinatorRunnable并没有直接通过new关键字创建对象,那么其构造器应该只有子类调用。下面到子类中找helper的定义。 CoordinatorHistoricalManagerRunnable这个类继承了CoordinatorRunnable抽象类,且只定义了一个有参构造器,就是这个构造器决定了父类的run方法还要做些什么事。 super( ImmutableList.of( new DruidCoordinatorSegmentInfoLoader(DruidCoordinator.this), new DruidCoordinatorHelper() { @Override public DruidCoordinatorRuntimeParams run(DruidCoordinatorRuntimeParams params) { ……//省略方法实现 } }, new DruidCoordinatorRuleRunner(DruidCoordinator.this), new DruidCoordinatorCleanupUnneeded(DruidCoordinator.this), new DruidCoordinatorCleanupOvershadowed(DruidCoordinator.this), new DruidCoordinatorBalancer(DruidCoordinator.this), new DruidCoordinatorLogger(DruidCoordinator.this) ), startingLeaderCounter );在这个构造器中传入了一个装载DruidCoordinatorHelper的列表。这个列表中helper对象会在DruidCoordinator的run方法实现中被调用,运行每个helper的run方法。其父类方法的实现中部分步骤延伸至子类,由子类提供实践方式,其实用的还是模板方法。DruidCoordinatorHelper并没有继承任何的接口或者类。也就是说CoordinatorRunnable的是在单个线程中调用helper的run方法。从这个helper中我们可以看出,这个CoordinatorHistoricalManagerRunnable主要是实现了segments负责均衡以及Rules管理,segments清理等功能。 CoordinatorIndexingServiceRunnable作为CoordinatorRunnable的另外一个子类,它的类定义就简单很多了。 public CoordinatorIndexingServiceRunnable(List<DruidCoordinatorHelper> helpers, final int startingLeaderCounter) { super(helpers, startingLeaderCounter); }这个类中helpers也是通过其构造器赋值的。但是原理是一样的,传什么样的helper就做什么样的事。 start()该方法由Lifecycle进行管理,注解为@LifecycleStart。在Coordinator启动后首先会在同步代码快中判断是Coordinator服务是有已经启动过了(可能配置了HA),如实没有启动则会去竞争leader。服务是否已经启动 是通过同步状态started来表示的。如果当前服务未启动,则将started置为true,然后就调用becomeLeader()方法来完成后续的操作。 public void start() { synchronized (lock) { if (started) { return; } started = true; coordLeaderSelector.registerListener( new DruidLeaderSelector.Listener() { @Override public void becomeLeader() { DruidCoordinator.this.becomeLeader(); } @Override public void stopBeingLeader() { DruidCoordinator.this.stopBeingLeader(); } } ); } }Coordiantor在宣布服务之前会先去对MetadataStorage中的一些元信息进行操作。 //扫描segment metadataSegmentManager.start();// metadataRuleManager.start();SQLMetadataSegmentManager之start方法 在这个方法中,主要的实现是在这个类的poll()方法中,而poll()方法是通过单个线程调度的,调度方式为scheduleWithFixedDelay。通过这种方式调度就必须要等当前的线程工作执行完毕之后,才会在指定的间隔之后进行下一次调度。也就是说单个线程的执行时间对整个的调度周期有较大的影响。这种方式有两个明显的目的: 1.当前线程的工作不会阻塞主线程的执行,即二者的操作是异步的;2.线程的工作可以周期性的执行。 因为必须要等当前的线程执行完之后才会进行下一次调度,所以这个地方它创建了只有一个线程的线程池。 //单个线程的线程池 exec = Execs.scheduledSingleThreaded("DatabaseSegmentManager-Exec--%d"); final Duration delay = config.get().getPollDuration().toStandardDuration(); //对单个线程进行调度 exec.scheduleWithFixedDelay( new Runnable() { @Override public void run() { readLock.lock(); try { if (localStartOrder == currentStartOrder) { poll(); } } catch (Exception e) { log.makeAlert(e, "uncaught exception in segment manager polling thread").emit(); } finally { readLock.unlock(); } } }, 0, delay.getMillis(), TimeUnit.MILLISECONDS );SQLMetadataRuleManager之start方法 调度方式与前面的start()方法相似,只是poll()方法中扫描的数据不一样了。前者是更新segments,这里是更新rules。 完成上述两个操作之后,Coordinator开始宣布服务。宣布服务成功之后,Coordiantor就开始真正执行对segment和历史节点的管理。首先,它创建了一个list,用来存放要干的事以及调度周期。 final List<Pair<? extends CoordinatorRunnable, Duration>> coordinatorRunnables = Lists.newArrayList();在list中Coordinator放入了CoordinatorRunnable的两个子类——CoordinatorHistoricalManagerRunnable和CoordinatorIndexingServiceRunnable。我们先了解一下这个list如何被使用。 for (final Pair<? extends CoordinatorRunnable, Duration> coordinatorRunnable : coordinatorRunnables) { ScheduledExecutors.scheduleWithFixedDelay( exec, config.getCoordinatorStartDelay(), coordinatorRunnable.rhs, new Callable<ScheduledExecutors.Signal>() { private final CoordinatorRunnable theRunnable = coordinatorRunnable.lhs; @Override public ScheduledExecutors.Signal call() { if (coordLeaderSelector.isLeader() && startingLeaderCounter == coordLeaderSelector.localTerm()) { theRunnable.run(); } if (coordLeaderSelector.isLeader() && startingLeaderCounter == coordLeaderSelector.localTerm()) { // (We might no longer be leader) return ScheduledExecutors.Signal.REPEAT; } else { return ScheduledExecutors.Signal.STOP; } } } ); }这里也是使用了scheduleWithFixedDelay调度,也就是说每个CoordinatorRunnable都是要等上一次执行结束之后才会进行下一次的调度。而且这里需要注意到是调度方法中的exec变量是一个容量为1的线程池。也就是说这里的调度相当于从主线程又单独创建一个线程来执行这些CoordinatorRunnable。这里采用单个线程池的线程进行调度一是为了保证主线程不会阻塞,另外是为确保数据操作的安全性。至此我们知道了,Coordinator在启动的时候会先尝试去竞争主节点资格,然后扫描元数据库,这里是异步的。接着会宣布服务。服务宣布后他会去将一些工作封装成CoordinatorRunnable,由另外一个线程去周期性的调度执行这些Runnable的run方法。而这里使用模板方法,在父类的run方法中会调用helper的run方法,而helper是通过子类来确定。所以,为了探究Coordinator接下来会做什么,我们就需要确认在创建其子类对象是,helper是如何实现的。查看CoordinatorHistoricalManagerRunnable的构造器实现: public CoordinatorHistoricalManagerRunnable(final int startingLeaderCounter) { super( ImmutableList.of( new DruidCoordinatorSegmentInfoLoader(DruidCoordinator.this), //使用匿名内部类创建了一个Helper对象 new DruidCoordinatorHelper() { @Override public DruidCoordinatorRuntimeParams run(DruidCoordinatorRuntimeParams params) { ……//省略方法的实现 } }, new DruidCoordinatorRuleRunner(DruidCoordinator.this), new DruidCoordinatorCleanupUnneeded(DruidCoordinator.this), new DruidCoordinatorCleanupOvershadowed(DruidCoordinator.this), new DruidCoordinatorBalancer(DruidCoordinator.this), new DruidCoordinatorLogger(DruidCoordinator.this) ), startingLeaderCounter ); }查看源码易知,这里实际上是创建了7个DruidCoordinatorHelper,我们可以理解成,Coordinator接下来会串行地完成这七件事。这里为了代码的易读性和整洁度,可以把这个匿名内部类拎出来单独实现一下。CoordinatorHistoricalManagerRunnable中完成的工作基本上是与segments管理及历史节点负载均衡相关的。具体的实现可以查看每个Runnable的run方法。 关于CoordinatorIndexingServiceRunnable,我们前面说过,它的helpers是通过调用方传入参数决定的。 if (indexingServiceClient != null) { coordinatorRunnables.add( Pair.of( new CoordinatorIndexingServiceRunnable( makeIndexingServiceHelpers(), startingLeaderCounter ), config.getCoordinatorIndexingPeriod() ) );//makeIndexingServiceHelpersde的实现如下: private List<DruidCoordinatorHelper> makeIndexingServiceHelpers() { List<DruidCoordinatorHelper> helpers = Lists.newArrayList(); helpers.add(new DruidCoordinatorSegmentInfoLoader(DruidCoordinator.this)); helpers.addAll(indexingServiceHelpers); log.info("Done making indexing service helpers [%s]", helpers); return ImmutableList.copyOf(helpers); }实际上是传入了两个helper,一个是DruidCoordinatorSegmentInfoLoader,用来加载segments信息。另一个是indexingServiceHelpers,负责完成segment的一些合并工作。最后调用LookupCoordinatorManager的start()方法,这里暂时先不讨论。 现在的问题是,列表中的CoordinatorRunnable是通过单个线程来进行调度的,而且helpers也是通过串行的方式实现的。那么,如果单次调度时间过长的话,则会导致很长时间后才会进行下一次的调度。这里我们考虑一下,Druid为什么会选择使用只有一个线程资源的线程池来调度所有的CoordinatorRunnable。在单个线程中他们实际上是串行的。串行的好处是,在进程中变量对于内存的可见性得到了保证,坏处是执行效率差。 如果需要对这里进行改造,串行改并行,我们首先要确定的就是共享内存中的变量有哪些,对共享变量的操作有哪些。 首先查看父类的run方法,再查看run方法调用的各个helper。细看代码我们不难发现,这里其实就是一个装饰器模式。每个help的run方法都会对params对象进行读写,且大部分都是读操作,只有在返回的时候进行了写操作。备注:把匿名内部类抽出来,单独实现,然后再作为参数传递,会让代码看起来整洁很多,如下: 更改前:public CoordinatorHistoricalManagerRunnable(final int startingLeaderCounter) { super( ImmutableList.of( new DruidCoordinatorSegmentInfoLoader(DruidCoordinator.this), //使用匿名内部类创建了一个Helper对象 new DruidCoordinatorHelper() { @Override public DruidCoordinatorRuntimeParams run(DruidCoordinatorRuntimeParams params) { ……//省略方法的实现 } }, new DruidCoordinatorRuleRunner(DruidCoordinator.this), new DruidCoordinatorCleanupUnneeded(DruidCoordinator.this), new DruidCoordinatorCleanupOvershadowed(DruidCoordinator.this), new DruidCoordinatorBalancer(DruidCoordinator.this), new DruidCoordinatorLogger(DruidCoordinator.this) ), startingLeaderCounter ); }更改后: public CoordinatorHistoricalManagerRunnable(final int startingLeaderCounter) { super( ImmutableList.of( new DruidCoordinatorSegmentInfoLoader(DruidCoordinator.this), new DruidCoordinatorPeonRunner(), new DruidCoordinatorRuleRunner(DruidCoordinator.this), new DruidCoordinatorCleanupUnneeded(DruidCoordinator.this), new DruidCoordinatorCleanupOvershadowed(DruidCoordinator.this), new DruidCoordinatorBalancer(DruidCoordinator.this), new DruidCoordinatorLogger(DruidCoordinator.this) ), startingLeaderCounter ); }转自 : https://zhuanlan.zhihu.com/p/96105088
-
Inndb如何实现事务https://bbs.huaweicloud.com/forum/thread-0253122604884602025-1-1.htmlgaussdb for opengauss的分布式版本里面,如何使用SQL查询DN节点的状态 https://bbs.huaweicloud.com/forum/thread-02106122204087380005-1-1.htmlMyISAM和InnoDB的区别https://bbs.huaweicloud.com/forum/thread-0265122007626955015-1-1.html事务的特性和隔离级别https://bbs.huaweicloud.com/forum/thread-0264122006556602008-1-1.html执行计划缓存,Prepared Statement性能跃升的秘密https://bbs.huaweicloud.com/forum/thread-0260120984551501094-1-1.html完全兼容DynamoDB协议!GaussDB(for Cassandra)为NoSQL注入新活力https://bbs.huaweicloud.com/forum/thread-0283123490921523009-1-1.htmlThe core dump path is an invalid directoryhttps://bbs.huaweicloud.com/forum/thread-0226123389014306007-1-1.htmlMyBatis-Plus分页——PageHelper和IPage介绍https://bbs.huaweicloud.com/forum/thread-02112123317015032039-1-1.htmlSpringBoot使用Sharding-JDBC实现读写分离https://bbs.huaweicloud.com/forum/thread-0234123314918975034-1-1.htmlMySQL主从配置详解https://bbs.huaweicloud.com/forum/thread-0208123297730862039-1-1.htmlMysql实现读写分离的方式https://bbs.huaweicloud.com/forum/thread-02106123296879721043-1-1.htmlgaussdb for opengauss的分布式版本里面,如何使用SQL获取各个组件信息https://bbs.huaweicloud.com/forum/thread-02112122213198330005-1-1.htmlGaussDB创建数据库时使用binary作为LC_COLLATE发生报错https://bbs.huaweicloud.com/forum/thread-0260120823978093081-1-1.html
-
数据复制服务(DRS)是一种易用、稳定、高效、用于数据同步的云服务,本节小课为您介绍,如何通过DRS将Oracle的数据同步到DDM。使用场景DRS实时同步功能一般用于建立数据同步通道,解决数据共享问题,也可以用于数据流式集成,具有数据转换能力。本实践中的选择均为测试简化基本操作,仅做参考,实际情况请用户按业务场景选择,更多关于DRS的使用场景请单击这里了解。部署架构本示例中,DRS源数据库为本地自建Oracle数据库,目标端为华为云上的分布式数据库中间件DDM,通过公网网络,将源端的数据同步到目标端,部署架构可参考下图。更多关于DRS的使用场景请单击这里了解。源端Oracle准备同步前需要在源库构造一些数据类型,供同步完成后验证数据。执行如下步骤在源库构造数据:1. 根据本地的Oracle数据库的IP地址,通过数据库连接工具连接数据库。2. 根据支持的数据类型,在源库执行语句构造数据。3. 创建一个测试用的用户。create user test_info identified by xxx;test_info为本次实践创建的用户,xxx为用户的密码,请根据实际情况替换。4. 给用户赋权。grant dba to test_info;5. 在当前用户下创建一个数据表。CREATE TABLE test_info.table3(ID INT,COL01 CHAR(100),COL02 NCHAR(100),PRIMARY KEY(ID));6. 插入两行数据。insert into test_info.table3 values(4,'huawei','xian');insert into test_info.table3 values(2,'DRS-test','test1');insert into test_info.table3 values(1,'huawei','xian');7. 使语句生效。commit;目标端DDM准备一、创建DDM实例1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 分布式数据库中间件 DDM”,进入DDM管理控制台。4. 在实例管理页面,单击页面右上方的“购买数据库中间件实例”。5. 按需设置实例相关信息和规格。6. 选择实例所属的VPC和安全组、配置数据库端口。7. 实例信息设置完成后,单击页面下方“立即购买”。8. 实例创建成功后,用户可以在“实例管理”页面对其进行查看和管理。数据库端口默认为5066,实例创建成功后可修改。当实例运行状态为“运行中”时,表示实例创建完成。二、创建RDS for MySQL实例本章节介绍创建DDM下关联的RDS for MySQL实例,创建步骤如下。1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 云数据库 RDS”。4. 单击“购买数据库实例”。5. 配置实例名称和实例基本信息。6. 选择实例规格。7. 选择实例所属的VPC和安全组、配置数据库端口。注意:RDS for MySQL实例的虚拟私有云(VPC)和子网必须和DDM实例保持一致。8. 配置实例密码。9. 单击“立即购买”。10. 返回云数据库实例列表。 当RDS实例运行状态为“正常”时,表示实例创建完成。三、创建逻辑库并关联RDS for MySQL实例1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 分布式数据库中间件 DDM”,进入DDM管理控制台。4. 在实例管理页面,单击实例对应操作栏的“创建逻辑库”。5. 在创建逻辑库页面,选择“逻辑库模式”、“逻辑库总分片数”,填写“逻辑库名称”,并选择要关联的DDM帐号、要关联的实例,单击“下一步”。本示例中逻辑库模式为单库,逻辑库名称为db_test。注意:DRS目前仅支持同步源端Oracle的数据到目标DDM,不支持同步源库表结构及其他数据库对象,用户需要在目标库根据源端数据库的表结构创建对应的逻辑库。四、创建DDM账号1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 分布式数据库中间件 DDM”,进入DDM管理控制台。4. 在实例管理页面,单击实例实例名称,进入实例基本信息页面。5. 在左侧导航栏选择“帐号管理”,进入帐号管理页面。6. 在帐号管理页面单击“创建DDM帐号”,在弹窗中填选帐号信息、关联的逻辑库和权限。DDM目标库账号所需要的权限可参考DRS使用须知中的数据库权限说明。7. 信息填写完成,单击“确定”即可创建成功。五、创建目标库表结构DRS目前仅支持同步源端Oracle的数据到目标DDM,不支持同步源库表结构及其他数据库对象。用户需要在目标库根据源端逻辑库的表结构,自行在目标库创建对应的表结构及索引。更对约束限制可参考DRS使用须知。1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 分布式数据库中间件 DDM”,进入DDM管理控制台。4. 在弹出的对话框中输入创建DDM账号中的实例用户名和密码,单击“测试连接”检查。5. 连接成功后单击“登录”,登录DDM实例。6. 单击创建逻辑库并关联RDS for MySQL实例中创建的逻辑库db_test。7. 在db_test库中执行如下语句,创建与源端结构一致的同名目标表table3。CREATE TABLE `db_test`.`table3`( ID INT, COL01 CHAR(100), COL02 NCHAR(100), PRIMARY KEY(ID));创建DRS同步任务本章节介绍如何创建DRS同步任务,将本地自建Oracle上的数据库同步到华为云DDM。在创建任务前,需要针对同步条件进行手工自检,以确保您的同步任务更加顺畅。本示例为同步到DDM入云同步,您可以参考DRS使用须知获取相关信息。1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 数据复制服务 DRS”。4. 单击“创建同步任务”。5. 填写同步任务参数: a、配置同步任务名称。 b、填写同步任务信息并选择目标库。 这里的目标库选择创建DDM实例所创建的DDM实例。 6. 单击“开始创建”。同步实例创建中,大约需要5-10分钟。7. 配置源库信息和目标库数据库密码。 c、配置源库信息,单击“测试连接”。当界面显示“测试成功”时表示连接成功。 d、配置目标库信息,单击“测试连接”。当界面显示“测试成功”时表示连接成功。 8. 单击“下一步”。9. 在“设置同步”页面,选择同步对象。流速模式:不限速。同步对象:表级同步在源库选择需要同步的数据库和表。本次实践中选择“test_info”中的“table3”表,设置同步到目标“db_test”中的“table3”中。 10. 单击“下一步”,在“预检查”页面,进行同步任务预校验,校验是否可进行同步。查看检查结果,如有不通过的检查项,需要修复不通过项后,单击“重新校验”按钮重新进行同步任务预校验。预检查完成后,且所有检查项结果均成功时,单击“下一步”.11. 单击“提交任务”。返回DRS实时同步管理,查看任务状态。启动中状态一般需要几分钟,请耐心等待。当状态变更为“全量中”,表示同步任务已经启动。说明:目前Oracle到DDM同步支持全量、全量+增量两种模式。如果创建的任务为全量同步,任务启动后先进行全量数据同步,数据同步完成后任务自动结束。如果创建的任务为全量+增量同步,任务启动后先进入全量同步,全量数据同步完成后进入增量同步状态。增量同步会持续性同步增量数据,不会自动结束。确认同步执行结果确认同步结果可参考如下两种方式:1. DRS会针对同步对象、数据等维度进行对比,从而给出同步结果,详情参见在DRS管理控制台查看同步结果。2. 直接登录数据库查看库、表、数据是否同步完成。手工确认数据同步情况,详情参见在DDM管理控制台查看同步结果。在DRS管理控制台查看同步结果1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 数据复制服务 DRS”。4. 单击DRS实例名称。5. 单击“同步对比”。6. 选择“对象级对比”页签,查看对象是否缺失。单击“开始对比”,对比完成后,查看对比结果。7. 选择“数据级对比”页签,查看同步对象行数是否一致。 a、单击“创建对比任务”。b、在弹出的对话框中选择对比类型、时间和对象。c、等待对比任务结束后,查看数据对比结果。d、如需查看对比明细,可单击对比任务后的“查看对比报表”。在DDM管理控制台查看同步结果1. 登录华为云控制台。2. 单击管理控制台左上角的图标,选择区域。3. 单击左侧的服务列表图标,选择“数据库 > 分布式数据库中间件 DDM”。4. 选择DDM实例,单击同步的目标实例的操作列的“登录”。5. 在弹出的对话框中输入密码,单击“测试连接”检查。6. 连接成功后单击“登录”。7. 查看并确认目标库名和表名等,确认相关数据是否同步完成。
-
在仅使用数据库实例进行日常开发活动时,RDS for MySQL支持通过暂时停止按需实例以节省费用。实例停止后,支持手动重新开启实例。实例停止后还会收费吗?实例停止后,虚拟机(VM)停止收费,其余资源包括弹性公网IP(EIP)、存储资源、数据库代理(Proxy)、以及备份都正常计费。如何停止实例?1. 在“实例管理”页面,在主实例上,选择“更多 > 停止实例”。2. 在停止实例弹框,单击“是”,停止实例。3. 实例状态为“已停止”,说明实例停止成功。 实例停止后,自动备份任务也会停止。如何开启实例?1. 在“实例管理”页面,在主实例上,选择“更多 > 开启实例”。2. 在开启实例弹框,单击“是”,开启实例。3. 实例状态为“正常”,说明实例开启成功。 实例开启后,会自动触发一次全量备份。
-
iot接入实物需要哪一些软件平台,特别是基础的
-
企业资源规划(ERP)管理系统是一种集成化的信息管理系统,它能够协助企业有效地管理业务流程。该系统整合了多个应用程序,如人力资源管理、财务管理、供应链管理、生产管理和销售管理等,以实现企业资源的高效管理。本篇文章将深入探讨ERP系统的作用和功能。一、企业资源规划(ERP)系统在企业管理中扮演着至关重要的角色,其作用主要体现在以下三个方面:1.数据和流程整合ERP系统能够整合企业各个部门的数据和流程,消除信息孤岛,提高信息共享和协作能力。这有助于企业在管理上更加高效和灵活,增强企业管理的统一性和协调性。特别是对于大型企业而言,ERP系统的数据和流程整合功能可以帮助消除部门之间的数据孤立问题,提高整体管理效率和质量。2.生产效率提升ERP系统可以优化生产流程,管理生产计划和物料需求,及时响应市场需求,提高生产效率。通过ERP系统,企业可以更好地控制生产成本,提高产品质量和客户满意度。生产管理是ERP系统最为关注的领域之一,因为它直接关系到企业核心业务的有效性和效率。ERP系统可以帮助企业管理生产流程,从而提高生产效率、减少生产成本、降低产品缺陷率、提高产品质量和客户满意度。3.财务管理增强ERP系统可以帮助企业实现财务管理的自动化,包括财务报表、采购和销售订单、库存和账户管理等。这有助于企业更好地掌握资金流动和财务状况,提高财务决策的准确性和时效性。财务管理是企业管理的重要组成部分之一,而ERP系统可以帮助企业提高财务管理的效率和准确性。通过ERP系统,企业可以实现财务自动化,从而减少人工操作、降低错误率、提高报表准确性和时效性。除此之外,ERP系统的核心功能主要包括人力资源管理、供应链管理和生产管理。ERP系统可以帮助企业更好地管理人力资源,提高员工的工作效率和满意度;优化供应链管理,提高供应商的配合度和响应速度,减少库存量和降低成本;管理生产流程,提高生产效率、减少生产成本、降低产品缺陷率、提高产品质量和客户满意度。二、使用ERP系统还可以帮助企业实现以下优势:1.更高的可扩展性:ERP系统可以根据企业的需求进行定制化开发,满足企业不同阶段的业务需求,实现更高的可扩展性。2.更好的安全性:ERP系统可以对企业的数据进行安全保护,防止数据泄露和恶意攻击,实现更好的安全性。3.更好的协作:ERP系统可以协调各部门的工作,提高协作效率,实现更好的协作。4.更好的客户服务:ERP系统可以整合客户信息,帮助企业了解客户的需求和欲望,提高客户服务质量,进而取得更大的市场份额和更大的商业机会。综上所述,ERP系统的应用可以帮助企业提高管理效率和质量,提高市场竞争力,进而实现企业的可持续发展。
-
云数据库RDS for MySQL支持参数模板的导入和导出功能。导入参数模板:导入后会生成一个新的参数模板,供您后期使用。导出参数模板:您可以将数据库实例参数列表,导出并生成一个新的参数模板,供您后期使用。或者将数据库实例参数列表(参数名称,值,描述)导出到CSV中,方便查看并分析。导入参数模板1. 登录管理控制台。2. 单击管理控制台左上角的图标,选择区域和项目。3. 单击页面左上角的图标,选择“数据库 > 云数据库 RDS”,进入RDS信息页面。4. 在“参数管理”页面,单击“导入参数模板”。5. 在弹出框中,选择本地设置好的参数列表(参数名称,值,描述),单击“确定”。文件只能单项导入,只支持csv格式,文件大小不能超过50K。导出实例的参数1. 登录管理控制台。2. 单击管理控制台左上角的图标,选择区域和项目。3. 单击页面左上角的图标,选择“数据库 > 云数据库 RDS”,进入RDS信息页面。4. 在“实例管理”页面,选择指定的实例,单击实例名称,进入实例的基本信息页面。5. 在左侧导航栏中选择“参数修改”,在“参数”页签单击“导出”。导出到参数模板。将该实例对应参数列表导出并生成一个参数模板,供您后期使用。在弹出框中,填写新参数模板名称和描述,单击“确定”。创建完成后,会生成一个新的参数模板,您可在“参数管理”页面的“自定义模板”页签对其进行管理。 参数模板名称长度在1~64个字符之间,区分大小写,可包含字母、数字、中划线、下划线或句点,不能包含其他特殊字符。参数模板的描述长度不能超过256个字符,且不能包含回车和>!<"&'=特殊字符。导出到文件。将该实例对应的参数模板信息(参数名称,值,描述)导出到CSV表中,方便用户查看并分析。在弹出框中,填写文件名称,单击“确定”。文件名称长度在4~81个字符之间,必须以字母开头,可以包含字母、数字、中划线或下划线和中文,不能包含其他特殊字符。导出参数模板1. 登录管理控制台。2. 单击管理控制台左上角的图标,选择区域和项目。3. 单击页面左上角的图标,选择“数据库 > 云数据库 RDS”,进入RDS信息页面。4. 在“参数管理”页面,选择“系统模板”或“自定义模板”,选择“更多 > 导出”。5. 在弹出框中,设置文件名,单击“确定”。文件名长度4~81个字符。
-
什么时候能用上盘古啊,怎么预约名额。
上滑加载中
推荐直播
-
空中宣讲会 2025年华为软件精英挑战赛
2025/03/10 周一 18:00-19:00
宸睿 华为云存储技术专家、ACM-ICPC WorldFinal经验 晖哥
2025华为软挑赛空中宣讲会重磅来袭!完整赛程首曝+命题天团硬核拆题+三轮幸运抽奖赢参赛助力礼包,与全国优秀高校开发者同台竞技,直通顶尖赛事起跑线!
即将直播
热门标签